Compare commits

...

12 Commits

Author SHA1 Message Date
Germán Jabloñski
e8c01a2c55 Merge remote-tracking branch 'origin/2.x' into remove-docs 2024-12-10 11:03:25 -03:00
Germán Jabloñski
5dcaf5f582 docs: remove docs from 2.x branch. Moved to the main branch. 2024-12-10 10:55:59 -03:00
Dan Ribbens
59ee821cec Revert "fix(plugin-form-builder): use escapeHTML on submission data in serializeLexical" (#9805)
Reverts payloadcms/payload#8110
2024-12-06 15:58:35 -05:00
Naoto Ikeno
4892d96515 fix(graphql): 500 error when querying hasMany field data that is filtered by access-control (#6519)
This PR closes #6518.
Just filtering null item from the `results` array makes thing work well.

## Description

<!-- Please include a summary of the pull request and any related issues
it fixes. Please also include relevant motivation and context. -->

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-12-06 10:25:09 -05:00
NorthBlue333
b0de37ba95 fix(plugin-form-builder): use escapeHTML on submission data in serializeLexical (#8110)
## Description

Fixes #8109 
Note this should also be merge in payload/beta!

- [X] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [X] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [X] Existing test suite passes locally with my changes

Signed-off-by: NorthBlue333 <north333@free.fr>
2024-12-06 10:01:32 -05:00
アルパカ
0cf96785bc fix: when publishing from a draft, only 10 were published. (#7906)
## Description
this PR close : https://github.com/payloadcms/payload/issues/7905

delete limit when updated
- 
<!-- Please include a summary of the pull request and any related issues
it fixes. Please also include relevant motivation and context. -->

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [ ] Chore (non-breaking change which does not add functionality)
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-12-06 09:55:56 -05:00
Rajiv Seelam
e7ae5f0a97 Fix typo in concepts (#6864)
## Description

In example demonstrating depth, it's for posts, but it mentions "user"
as an example

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] This change requires a documentation update
2024-12-06 09:35:13 -05:00
Vid Čufar
7039b1f82e feat(i18n): add Slovenian (sl) translation (#8709)
This PR introduces Slovenian (sl) language translation.
2024-12-06 09:06:54 -05:00
Elliot DeNolf
67ce92cf2e chore(release): plugin-form-builder/1.2.3 [skip ci] 2024-12-03 08:52:34 -05:00
Elliot DeNolf
216e4ef077 ci: release script default to 'payload-2' tag 2024-12-03 08:52:12 -05:00
Braian Pita
6f453ea05a docs: email documentation example for SMTP setup (#5693)
The SMTP code example had a typo and was using the wrong environment variable to set the port for the email plugin
2024-12-02 23:22:38 -05:00
Elliot DeNolf
cf6a5df926 chore(release): plugin-seo/2.4.0 [skip ci] 2024-11-11 15:20:06 -05:00
108 changed files with 685 additions and 16513 deletions

View File

@@ -1,186 +0,0 @@
---
title: Collection Access Control
label: Collections
order: 20
desc: With Collection-level Access Control you can define which users can create, read, update or delete Collections.
keywords: collections, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, express
---
You can define Collection-level Access Control within each Collection's `access` property. All Access Control functions accept one `args` argument.
## Available Controls
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------------- |
| **[`create`](#create)** | Used in the `create` operation |
| **[`read`](#read)** | Used in the `find` and `findByID` operations |
| **[`update`](#update)** | Used in the `update` operation |
| **[`delete`](#delete)** | Used in the `delete` operation |
#### Auth-enabled Controls
If a Collection supports [`Authentication`](/docs/authentication/overview), the following Access Controls become available:
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------------------------------- |
| **[`admin`](#admin)** | Used to restrict access to the Payload Admin panel |
| **[`unlock`](#unlock)** | Used to restrict which users can access the `unlock` operation |
**Example Collection config:**
```ts
import { CollectionConfig } from 'payload/types';
export const Posts: CollectionConfig = {
slug: "posts",
// highlight-start
access: {
create: ({ req: { user } }) => { ... },
read: ({ req: { user } }) => { ... },
update: ({ req: { user } }) => { ... },
delete: ({ req: { user } }) => { ... },
admin: ({ req: { user } }) => { ... },
},
// highlight-end
};
```
### Create
Returns a boolean which allows/denies access to the `create` request.
**Available argument properties:**
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The data passed to create the document with. |
**Example:**
```ts
const PublicUsers = {
slug: 'public-users',
access: {
// highlight-start
// allow guest users to self-registration
create: () => true,
// highlight-end
...
},
fields: [ ... ],
}
```
### Read
Read access functions can return a boolean result or optionally return a [query constraint](/docs/queries/overview) which limits the documents that are returned to only those that match the constraint you provide. This can be helpful to restrict users' access to only certain documents however you specify.
**Available argument properties:**
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of document requested, if within `findByID` |
**Example:**
```ts
import { Access } from 'payload/config'
const canReadPage: Access = ({ req: { user } }) => {
// allow authenticated users
if (user) {
return true
}
// using a query constraint, guest users can access when a field named 'isPublic' is set to true
return {
// assumes we have a checkbox field named 'isPublic'
isPublic: {
equals: true,
},
}
}
```
### Update
Update access functions can return a boolean result or optionally return a [query constraint](/docs/queries/overview) to limit the document(s) that can be updated by the currently authenticated user. For example, returning a `query` from the `update` Access Control is helpful in cases where you would like to restrict a user to only being able to update the documents containing a `createdBy` relationship field equal to the user's ID.
**Available argument properties:**
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of document requested to update |
| **`data`** | The data passed to update the document with |
**Example:**
```ts
import { Access } from 'payload/config'
const canUpdateUser: Access = ({ req: { user }, id }) => {
// allow users with a role of 'admin'
if (user.roles && user.roles.some((role) => role === 'admin')) {
return true
}
// allow any other users to update only oneself
return user.id === id
}
```
### Delete
Similarly to the Update function, returns a boolean or a [query constraint](/docs/queries/overview) to limit which documents can be deleted by which users.
**Available argument properties:**
| Option | Description |
| --------- | --------------------------------------------------------------------------------------------------- |
| **`req`** | The Express `request` object with additional `user` property, which is the currently logged in user |
| **`id`** | `id` of document requested to delete |
**Example:**
```ts
import { Access } from 'payload/config'
const canDeleteCustomer: Access = async ({ req, id }) => {
if (!id) {
// allow the admin UI to show controls to delete since it is indeterminate without the id
return true
}
// query another collection using the id
const result = await req.payload.find({
collection: 'contracts',
limit: 0,
depth: 0,
where: {
customer: { equals: id },
},
})
return result.totalDocs === 0
}
```
### Admin
If the Collection is [used to access the Payload Admin panel](/docs/admin/overview#the-admin-user-collection), the `Admin` Access Control function determines whether or not the currently logged in user can access the admin UI.
**Available argument properties:**
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
### Unlock
Determines which users can [unlock](/docs/authentication/operations#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](/docs/authentication/config#options).
**Available argument properties:**
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |

View File

@@ -1,81 +0,0 @@
---
title: Field-level Access Control
label: Fields
order: 30
desc: Field-level Access Control is specified within a field's config, and allows you to define which users can create, read or update Fields.
keywords: fields, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Field Access Control is specified with functions inside a field's config. All field-level Controls return a boolean value to allow or deny access for the specified operation. No field-level Access Controls support returning query constraints. All Access Control functions accept one `args` argument.
## Available Controls
| Function | Purpose |
| ----------------------- | -------------------------------------------------------------------------------- |
| **[`create`](#create)** | Allows or denies the ability to set a field's value when creating a new document |
| **[`read`](#read)** | Allows or denies the ability to read a field's value |
| **[`update`](#update)** | Allows or denies the ability to update a field's value |
**Example Collection config:**
```ts
import { CollectionConfig } from 'payload/types';
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
// highlight-start
access: {
create: ({ req: { user } }) => { ... },
read: ({ req: { user } }) => { ... },
update: ({ req: { user } }) => { ... },
},
// highlight-end
};
],
};
```
### Create
Returns a boolean which allows or denies the ability to set a field's value when creating a new document. If `false` is returned, any passed values will be discarded.
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The full data passed to create the document. |
| **`siblingData`** | Immediately adjacent field data passed to create the document. |
### Read
Returns a boolean which allows or denies the ability to read a field's value. If `false`, the entire property is omitted from the resulting document.
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of the document being read |
| **`doc`** | The full document data. |
| **`siblingData`** | Immediately adjacent field data of the document being read. |
### Update
Returns a boolean which allows or denies the ability to update a field's value. If `false` is returned, any passed values will be discarded.
If `false` is returned and you attempt to update the field's value, the operation will **not** throw an error however the field will be omitted from the update operation and the value will remain unchanged.
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of the document being updated |
| **`data`** | The full data passed to update the document. |
| **`siblingData`** | Immediately adjacent field data passed to update the document with. |
| **`doc`** | The full document data, before the update is applied. |

View File

@@ -1,61 +0,0 @@
---
title: Globals Access Control
label: Globals
order: 40
desc: Global-level Access Control is specified within each Global's `access` property and allows you to define which users can read or update Globals.
keywords: globals, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, express
---
You can define Global-level Access Control within each Global's `access` property. All Access Control functions accept one `args` argument.
\*\*Available argument properties:
## Available Controls
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------- |
| **[`read`](#read)** | Used in the `findOne` Global operation |
| **[`update`](#update)** | Used in the `update` Global operation |
**Example Global config:**
```ts
import { GlobalConfig } from 'payload/types'
const Header: GlobalConfig = {
slug: 'header',
// highlight-start
access: {
read: ({ req: { user } }) => {
/* */
},
update: ({ req: { user } }) => {
/* */
},
},
// highlight-end
}
export default Header
```
### Read
Returns a boolean result or optionally a [query constraint](/docs/queries/overview) which limits who can read this global based on its current properties.
**Available argument properties:**
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
### Update
Returns a boolean result or optionally a [query constraint](/docs/queries/overview) which limits who can update this global based on its current properties.
**Available argument properties:**
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The data passed to update the global with. |

View File

@@ -1,86 +0,0 @@
---
title: Access Control
label: Overview
order: 10
desc: Payload makes it simple to define and manage access control. By declaring roles, you can set permissions and restrict what your users can interact with.
keywords: overview, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a `boolean` or a [`query`](/docs/queries/overview) constraint to restrict which documents users can interact with.
<YouTube id="DoPLyXG26Dg" title="Overview of Payload Access Control" />
**Example use cases:**
- Allowing anyone `read` access to all `Post`s
- Only allowing public access to `Post`s where a `status` field is equal to `published`
- Giving only `User`s with a `role` field equal to `admin` the ability to delete `Page`(s)
- Allowing anyone to create `ContactSubmission`s, but only logged in users to `read`, `update` or `delete` them
- Restricting a `User` to only be able to see their own `Order`(s), but no others
- Allowing `User`s that belong to a certain `Organization` to access only that `Organization`'s `Resource`s
### Default Settings
**By default, all Collections and Globals require that a user is logged in to be able to interact in any way.** The default Access Control function evaluates the `user` from the Express `req` and returns `true` if a user is logged in, and `false` if not.
**Default Access function:**
```ts
const defaultPayloadAccess = ({ req: { user } }) => {
// Return `true` if a user is found
// and `false` if it is undefined or null
return Boolean(user)
}
```
<Banner type="success">
<strong>Note:</strong>
<br />
In the Local API, all Access Control functions are skipped by default, allowing your server to do
whatever it needs. But, you can opt back in by setting the option
{' '}
<strong>
overrideAccess
</strong>
{' '}
to <strong>false</strong>.
</Banner>
### Access Control Types
You can manage access within Payload on three different levels:
- [Collections](/docs/access-control/collections)
- [Fields](/docs/access-control/fields)
- [Globals](/docs/access-control/globals)
### When Access Control is Executed
<Banner type="success">
<strong>Note:</strong>
<br />
Access control functions are utilized in two places. It's important to understand how and when
your access control is executed.
</Banner>
#### As you execute operations
When you perform Payload operations like `create`, `read`, `update`, and `delete`, your access control functions will be executed before any changes or operations are completed.
#### Within the Admin UI
The Payload Admin UI responds dynamically to the access control that you define. For example, if you restrict editing a `ExampleCollection` to only users that feature a `role` of `admin`, the Payload Admin UI will **hide** the `ExampleCollection` from the Admin UI entirely. This is super powerful and allows you to control who can do what with your Admin UI.
To accomplish this, Payload ships with an `Access` operation, which is executed when a user logs into the Admin UI. Payload will execute each one of your access control functions, across all collections, globals, and fields, at the top level and return a response that contains a reflection of what the currently authenticated user can do with your application.
### Argument Availability
<Banner type="warning">
<strong>Important:</strong>
<br />
When your access control functions are executed via the <strong>access</strong> operation, the{' '}
<strong>id</strong> and <strong>data</strong> arguments will be <strong>undefined</strong>,
because Payload is executing your functions without referencing a specific document.
</Banner>
If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your access control is being executed via the `access` operation, to determine solely what the user can do within the Admin UI.

View File

@@ -1,54 +0,0 @@
---
title: Bundlers
label: Bundlers
order: 60
desc: Bundlers are used to bundle the code that serves Payload's Admin Panel.
---
Payload has two official bundlers, the [Webpack Bundler](/docs/admin/webpack) and the [Vite Bundler](/docs/admin/vite). You must install a bundler to use the admin panel.
##### Install a bundler
Webpack (recommended):
```text
yarn add @payloadcms/bundler-webpack
```
Vite (beta):
```text
yarn add @payloadcms/bundler-vite
```
##### Configure the bundler
```ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { webpackBundler } from '@payloadcms/bundler-webpack'
// import { viteBundler } from '@payloadcms/bundler-vite'
export default buildConfig({
// highlight-start
admin: {
bundler: webpackBundler() // or viteBundler()
},
// highlight-end
})
```
### What are bundlers?
At their core, a bundler's main goal is to take a bunch of files and turn them into a few optimized files that you ship to the browser. The admin UI has a root `index.html` entry point, and from there the bundler traverses the dependency tree, bundling all of the files that are required from that point on.
Since the bundled file is sent to the browser, it can't include any server-only code. You will need to remove any server-only code from your admin UI before bundling it. You can learn more about [excluding server code](/docs/admin/excluding-server-code) section.
<Banner type="warning">
<strong>Using environment variables in the admin UI</strong>
<br />
Bundles should not contain sensitive information. By default, Payload
excludes env variables from the bundle. If you need to use env variables in your payload config,
you need to prefix them with `PAYLOAD_PUBLIC_` to make them available to the client-side code.
</Banner>

View File

@@ -1,711 +0,0 @@
---
title: Swap in your own React components
label: Custom Components
order: 20
desc: Fully customize your Admin Panel by swapping in your own React components. Add fields, remove views, update routes and change functions to sculpt your perfect Dashboard.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
---
While designing the Payload Admin panel, we determined it should be as minimal and straightforward as possible to allow easy customization and control. There are many times where you may want to completely control how a whole view or a field works. You might even want to add in new views entirely. In order for Payload to support this level of customization without introducing versioning / future-proofing issues, Payload provides for a pattern to supply your own React components via your Payload config.
To swap in your own React component, first, consult the list of available component overrides below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly.
<Banner type="success">
<strong>Tip:</strong>
<br />
Custom components will automatically be provided with all props that the default component normally
accepts.
</Banner>
### Base Component Overrides
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:
| 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 Payload Admin UI. [More](#custom-providers) |
| **`actions`** | Array of custom components to be rendered in the Payload Admin UI header, providing additional interactivity and functionality. |
| **`views`** | Override or create new views within the Payload Admin UI. [More](#views) |
Here is a full example showing how to swap some of these components for your own.
`payload.config.js`
```ts
import { buildConfig } from 'payload/config'
import {
MyCustomNav,
MyCustomLogo,
MyCustomIcon,
MyCustomAccount,
MyCustomDashboard,
MyProvider,
MyCustomAdminAction,
} from './customComponents'
export default buildConfig({
admin: {
components: {
Nav: MyCustomNav,
graphics: {
Icon: MyCustomIcon,
Logo: MyCustomLogo,
},
actions: [MyCustomAdminAction],
views: {
Account: MyCustomAccount,
Dashboard: MyCustomDashboard,
},
providers: [MyProvider],
},
},
})
```
#### Views
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:
| 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. |
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. Each view corresponds to its own `<Route />` component in [React Router v5](https://v5.reactrouter.com). Payload exposes all of the properties of React Router:
| Property | Description |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| **`Component`** \* | Pass in the component that should be rendered when a user navigates to this route. |
| **`path`** \* | React Router `path`. [See the React Router docs](https://v5.reactrouter.com/web/api/Route/path-string-string) for more info. |
| **`exact`** | React Router `exact` property. [More](https://v5.reactrouter.com/web/api/Route/exact-bool) |
| **`strict`** | React Router `strict` property. [More](https://v5.reactrouter.com/web/api/Route/strict-bool) |
| **`sensitive`** | React Router `sensitive` property. [More](https://v5.reactrouter.com/web/api/Route/sensitive-bool) |
_\* An asterisk denotes that a property is required._
#### Adding new views
To add a _new_ view to the Admin Panel, simply add another key to the `views` object with at least a `path` and `Component` property. For example:
```ts
// payload.config.ts
{
// ...
admin: {
components: {
views: {
MyCustomView: {
Component: MyCustomView,
path: '/my-custom-view',
},
},
},
},
}
```
<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.
</Banner>
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/main/test/admin/components)._
For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component).
### Collections
You can override components on a collection-by-collection basis via the `admin.components` property.
| Path | Description |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **`BeforeList`** | Array of components to inject _before_ the built-in List view |
| **`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 within the Payload Admin UI. [More](#collection-views) |
Here is a full example showing how to swap some of these components for your own:
`Collection.ts`
```tsx
import * as React from 'react'
import {
CustomSaveButtonProps,
CustomSaveDraftButtonProps,
CustomPublishButtonType,
CustomPreviewButtonProps,
} from 'payload/types'
export const CustomSaveButton: CustomSaveButtonProps = ({ DefaultButton, label, save }) => {
return <DefaultButton label={label} save={save} />
}
export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({
DefaultButton,
disabled,
label,
saveDraft,
}) => {
return <DefaultButton label={label} disabled={disabled} saveDraft={saveDraft} />
}
export const CustomPublishButton: CustomPublishButtonType = ({
DefaultButton,
disabled,
label,
publish,
}) => {
return <DefaultButton label={label} disabled={disabled} publish={publish} />
}
export const CustomPreviewButton: CustomPreviewButtonProps = ({
DefaultButton,
disabled,
label,
preview,
}) => {
return <DefaultButton label={label} disabled={disabled} preview={preview} />
}
export const MyCollection: SanitizedCollectionConfig = {
slug: 'my-collection',
admin: {
components: {
edit: {
SaveButton: CustomSaveButton,
SaveDraftButton: CustomSaveDraftButton,
PublishButton: CustomPublishButton,
PreviewButton: CustomPreviewButton,
},
},
}
}
```
#### Collection views
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:
| 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. |
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_.
```ts
// Collection.ts
{
// ...
admin: {
components: {
views: {
Edit: MyCustomEditView,
List: MyCustomListView,
},
},
},
}
```
_For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component)._
**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
As with Collections, you can override components on a global-by-global basis via the `admin.components` property.
| 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 within the Payload Admin UI. [More](#global-views) |
#### Global views
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:
| Property | Description |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| **`Edit`** | The Edit view is used to edit a single document for a given Global. |
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_.
```ts
// Global.ts
{
// ...
admin: {
components: {
views: {
Edit: MyCustomEditView,
},
},
},
}
```
_For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component)._
**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
{
// ...
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
},
},
},
},
},
}
```
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.
### 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:
| 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) |
| **`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) |
Here is an example:
```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,
},
},
},
},
}
```
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: {
components: {
views: {
Edit: {
MyCustomTab: {
Component: MyCustomTab,
path: '/my-custom-tab',
// You an swap the entire tab component out for your own
Tab: MyCustomTab
},
AnotherCustomView: {
Component: AnotherCustomView,
path: '/another-custom-view',
// Or you can use the default tab component and just pass in your own label and href
Tab: {
label: 'Another Custom View',
href: '/another-custom-view',
}
},
},
},
},
},
}
```
### Building a custom view component
Your custom view components will be given all the props that a React Router `<Route />` typically would receive, as well as two props from Payload:
| 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 or not. |
<Banner type="warning">
<strong>Note:</strong>
<br />
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>
#### Example
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).
### Fields
All Payload fields support the ability to swap in your own React components. So, for example, instead of rendering a default Text input, you might need to render a color picker that provides the editor with a custom color picker interface to restrict the data entered to colors only.
<Banner type="success">
<strong>Tip:</strong>
<br />
Don't see a built-in field type that you need? Build it! Using a combination of custom validation
and custom components, you can override the entirety of how a component functions within the admin
panel and effectively create your own field type.
</Banner>
**Fields support the following custom components:**
| 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) |
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 Error 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) |
## Cell Component
These are the props that will be passed to your custom Cell to use in your own components.
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`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. |
| **`cellData`** | The data for the field that the cell represents. |
| **`rowData`** | An object with all the field values for the row. |
#### Example
```tsx
import React from 'react'
import type { Props } from 'payload/components/views/Cell'
import './index.scss'
const baseClass = 'custom-cell'
const CustomCell: React.FC<Props> = (props) => {
const { field, colIndex, collection, cellData, rowData } = props
return <span className={baseClass}>{cellData}</span>
}
```
## Field Component
When writing your own custom components you can make use of a number of hooks to set data, get reactive changes to other fields, get the id of the document or interact with a context from a custom provider.
### Sending and receiving values from the form
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'
const CustomTextField: React.FC<{ path: string }> = ({ path }) => {
// highlight-start
const { value, setValue } = useField<string>({ path })
// highlight-end
return <input onChange={(e) => setValue(e.target.value)} value={value} />
}
```
<Banner type="success">
For more information regarding the hooks that are available to you while you build custom
components, including the <strong>useField</strong> hook, [click here](/docs/admin/hooks).
</Banner>
## Label Component
These are the props that will be passed to your custom Label.
| Property | Description |
| ---------------- | ---------------------------------------------------------------- |
| **`htmlFor`** | Property used to set `for` attribute for label. |
| **`label`** | Label value provided in field, it can be used with i18n. |
| **`required`** | A boolean value that represents if the field is required or not. |
#### Example
```tsx
import React from 'react'
import { useTranslation } from 'react-i18next'
import { getTranslation } from 'payload/utilities/getTranslation'
type Props = {
htmlFor?: string
label?: Record<string, string> | false | string
required?: boolean
}
const CustomLabel: React.FC<Props> = (props) => {
const { htmlFor, label, required = false } = props
const { i18n } = useTranslation()
if (label) {
return (<span>
{getTranslation(label, i18n)}
{required && <span className="required">*</span>}
</span>);
}
return null
}
```
## Error Component
These are the props that will be passed to your custom Error.
| Property | Description |
| ---------------- | ------------------------------------------------------------- |
| **`message`** | The error message. |
| **`showError`** | A boolean value that represents if the error should be shown. |
#### Example
```tsx
import React from 'react'
type Props = {
message: string
showError?: boolean
}
const CustomError: React.FC<Props> = (props) => {
const { message, showError } = props
if (showError) {
return <p style={{color: 'red'}}>{message}</p>
} else return null;
}
```
## afterInput and beforeInput
With these properties you can add multiple components before and after the input element. For example, you can add an absolutely positioned button to clear the current field value.
#### Example
```tsx
import React from 'react'
import { Field } from 'payload/types'
import './style.scss'
const ClearButton: React.FC = () => {
return <button onClick={() => {/* ... */}}>X</button>
}
const titleField: Field = {
name: 'title',
type: 'text',
admin: {
components: {
afterInput: [ClearButton]
}
}
}
export default titleField;
```
## Custom providers
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more.
<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>
### 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-in 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 `react-i18next` in your components.
For example:
```tsx
import { useTranslation } from 'react-i18next'
const CustomComponent: React.FC = () => {
// highlight-start
const { t, i18n } = useTranslation('namespace1')
// highlight-end
return (
<ul>
<li>{t('key', { variable: 'value' })}</li>
<li>{t('namespace2:key', { variable: 'value' })}</li>
<li>{i18n.language}</li>
</ul>
)
}
```
### Getting the current locale
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:
```tsx
import { useLocale } from 'payload/components/utilities'
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale()
// highlight-end
const trans = {
en: 'Hello',
es: 'Hola',
}
return <span> {trans[locale.code]} </span>
}
```

View File

@@ -1,51 +0,0 @@
---
title: Customizing CSS & SCSS
label: Customizing CSS
order: 40
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.
keywords: admin, css, scss, documentation, Content Management System, cms, headless, javascript, node, react, express
---
### Adding your own CSS / SCSS
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.
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
**Example in payload.config.js:**
```ts
import { buildConfig } from 'payload/config'
import path from 'path'
const config = buildConfig({
admin: {
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
},
})
```
### 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.
</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.

View File

@@ -1,27 +0,0 @@
---
title: Environment Variables in Admin UI
label: Environment Variables
order: 100
desc: NEEDS TO BE WRITTEN
---
## Admin environment vars
<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.
</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.
**Payload will automatically supply any present `env` variables that are prefixed with `PAYLOAD_PUBLIC_` directly to the Admin panel.**
For example, if you've got the following environment variable:
`PAYLOAD_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`.

View File

@@ -1,206 +0,0 @@
---
title: Excluding server-only code from admin UI
label: Excluding server code
order: 70
desc: Learn how to exclude server-only code from the Payload Admin UI bundle
---
Because the Admin Panel browser bundle includes your Payload Config file, files using server-only modules need to be excluded.
It's common for your config to rely on server only modules to perform logic in access control functions, hooks, and other contexts.
Any file that imports a server-only module such as `fs`, `stripe`, `authorizenet`, `nodemailer`, etc. **cannot** be included in the browser bundle.
#### Example Scenario
Say we have a collection called `Subscriptions` that has a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload.
**Collection config**:
```ts
// collections/Subscriptions/index.ts
import { CollectionConfig } from 'payload/types'
import createStripeSubscription from './hooks/createStripeSubscription'
export const Subscription: CollectionConfig = {
slug: 'subscriptions',
hooks: {
beforeChange: [createStripeSubscription],
},
fields: [
{
name: 'stripeSubscriptionID',
type: 'text',
required: true,
},
],
}
```
**Collection hook**:
```ts
// collections/Subscriptions/hooks/createStripeSubscription.ts
// highlight-start
import Stripe from 'stripe' // <-- server-only module
// highlight-end
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
export const createStripeSubscription = async ({ data, operation }) => {
if (operation === 'create') {
const dataWithStripeID = { ...data }
// use Stripe to create a Stripe subscription
const subscription = await stripe.subscriptions.create({
// Configure the subscription accordingly
})
// Automatically add the Stripe subscription ID
// to the data that will be saved to this Subscription doc
dataWithStripeID.stripeSubscriptionID = subscription.id
return dataWithStripeID
}
return data
}
```
<Banner type="error">
<strong>Warning:</strong>
<br />
The above code is NOT production-ready and should not be referenced to create Stripe
subscriptions. Although creating a beforeChange hook is a completely valid spot to do things like
create subscriptions, the code above is incomplete and insecure, meant for explanation purposes
only.
</Banner>
**As-is, this collection will prevent your Admin panel from bundling or loading correctly, because Stripe relies on some Node-only packages.**
#### How to fix this
You need to make sure that you use `alias`es to tell your bundler to import "safe" files vs. attempting to import any server-side code that you need to get rid of. Depending on your bundler (Webpack, Vite, etc.) the steps involved may be slightly different.
The basic idea is to create a file that exports an empty object, and then alias import paths of any files that import server-only modules to that empty object file.
This way when your bundler goes to import a file that contains server-only modules, it will instead import the empty object file, which will not break the browser bundle.
### Aliasing server-only modules
To remove files that contain server-only modules from your bundle, you can use an `alias`.
In the Subscriptions config file above, we are importing the hook like so:
```ts
// collections/Subscriptions/index.ts
import createStripeSubscription from './hooks/createStripeSubscription'
```
By default the browser bundle will now include all the code from that file and any files down the tree. We know that the file imports `stripe`.
To fix this, we need to alias the `createStripeSubscription` file to a different file that can safely be included in the browser bundle.
First, we will create a mock file to replace the server-only file when bundling:
```js
// mocks/modules.js
export default {}
/**
* NOTE: if you are destructuring an import
* the mock file will need to export matching
* variables as the destructured object.
*
* export const namedExport = {}
*/
```
Aliasing with [Webpack](/docs/admin/webpack) can be done by:
```ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { webpackBundler } from '@payloadcms/bundler-webpack'
import { Subscriptions } from './collections/Subscriptions'
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js')
const fullFilePath = path.resolve(
__dirname,
'collections/Subscriptions/hooks/createStripeSubscription'
)
export default buildConfig({
collections: [Subscriptions],
admin: {
bundler: webpackBundler(),
webpack: (config) => {
return {
...config,
resolve: {
...config.resolve,
// highlight-start
alias: {
...config.resolve.alias,
[fullFilePath]: mockModulePath,
},
// highlight-end
},
}
},
},
})
```
Aliasing with [Vite](/docs/admin/vite) can be done by:
```ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { viteBundler } from '@payloadcms/bundler-vite'
import { Subscriptions } from './collections/Subscriptions'
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js')
export default buildConfig({
collections: [Subscriptions],
admin: {
bundler: viteBundler(),
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
// highlight-start
// Add your own aliases using the find and replacement keys
// remember, vite aliases are exact-match only
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
// highlight-end
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
},
})
```

View File

@@ -1,873 +0,0 @@
---
title: React Hooks
label: React Hooks
order: 30
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, express
---
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.
### useField
The `useField` hook is used internally within every applicable Payload field component, and it manages sending and receiving a field's state from its parent form.
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'
type Props = { path: string }
const CustomTextField: React.FC<Props> = ({ path }) => {
// highlight-start
const { value, setValue } = useField<string>({ path })
// highlight-end
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:
```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
```
### useFormFields
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
<Banner type="success">
<strong>This hook is great for retrieving only certain fields from form state</strong> because it
ensures that it will only cause a rerender when the items that you ask for change.
</Banner>
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
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'
const MyComponent: React.FC = () => {
// Get only the `amount` field state, and only cause a rerender when that field changes
const amount = useFormFields(([fields, dispatch]) => fields.amount)
// Do the same thing as above, but to the `feePercentage` field
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage)
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
return <span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
}
}
```
### useAllFormFields
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
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';
const ExampleComponent: React.FC = () => {
// the `fields` const will be equal to all fields' state,
// and the `dispatchFields` method is usable to send field state up to the form
const [fields, dispatchFields] = useAllFormFields();
// Pass in fields, and indicate if you'd like to "unflatten" field data.
// The result below will reflect the data stored in the form at the given time
const formData = reduceFieldsToValues(fields, true);
// Pass in field state and a path,
// and you will be sent all sibling data of the path that you've specified
const siblingData = getSiblingData(fields, 'someFieldName');
return (
// return some JSX here if necessary
)
};
```
##### 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`.
You can send the following actions to the `dispatchFields` function.
| Action | Description |
| ---------------------- | -------------------------------------------------------------------------- |
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
| **`MOVE_ROW`** | Moves a row of data (useful in array / block field data) |
| **`REMOVE`** | Removes a field from form state |
| **`REMOVE_ROW`** | Removes a row of data from form state (useful in array / block field data) |
| **`REPLACE_STATE`** | Completely replaces form state |
| **`UPDATE`** | Update any property of a specific field's state |
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/components/forms/Form/types.ts).
### useForm
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
<Banner type="warning">
<strong>Warning:</strong>
<br />
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields`
property will be out of date. You should only leverage this hook if you need to perform actions
against the form in response to your users' actions. Do not rely on its returned "fields" as being
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: |
<TableWithDrawers
columns={[
'Action',
'Description',
'Example',
]}
rows={[
[
{
value: <strong><code>fields</code></strong>,
},
{
value: "Deprecated. This property cannot be relied on as up-to-date.",
},
{
value: ''
}
],
[
{
value: <strong><code>submit</code></strong>,
},
{
value: "Method to trigger the form to submit",
},
{
value: ''
}
],
[
{
value: <strong><code>dispatchFields</code></strong>,
},
{
value: "Dispatch actions to the form field state",
},
{
value: ''
}
],
[
{
value: <strong><code>validateForm</code></strong>,
},
{
value: "Trigger a validation of the form state",
},
{
value: ''
}
],
[
{
value: <strong><code>createFormData</code></strong>,
},
{
value: <>Create a <code>multipart/form-data</code> object from the current form's state</>,
},
{
value: ''
}
],
[
{
value: <strong><code>disabled</code></strong>,
},
{
value: "Boolean denoting whether or not the form is disabled",
},
{
value: ''
}
],
[
{
value: <strong><code>getFields</code></strong>,
},
{
value: 'Gets all fields from state',
},
{
value: '',
}
],
[
{
value: <strong><code>getField</code></strong>,
},
{
value: 'Gets a single field from state by path',
},
{
value: '',
},
],
[
{
value: <strong><code>getData</code></strong>,
},
{
value: 'Returns the data stored in the form',
},
{
value: '',
},
],
[
{
value: <strong><code>getSiblingData</code></strong>,
},
{
value: 'Returns form sibling data for the given field path',
},
{
value: '',
},
],
[
{
value: <strong><code>setModified</code></strong>,
},
{
value: <>Set the form\'s <code>modified</code> state</>,
},
{
value: '',
},
],
[
{
value: <strong><code>setProcessing</code></strong>,
},
{
value: <>Set the form\'s <code>processing</code> state</>,
},
{
value: '',
},
],
[
{
value: <strong><code>setSubmitted</code></strong>,
},
{
value: <>Set the form\'s <code>submitted</code> state</>,
},
{
value: '',
},
],
[
{
value: <strong><code>formRef</code></strong>,
},
{
value: 'The ref from the form HTML element',
},
{
value: '',
},
],
[
{
value: <strong><code>reset</code></strong>,
},
{
value: 'Method to reset the form to its initial state',
},
{
value: '',
},
],
[
{
value: <strong><code>addFieldRow</code></strong>,
},
{
value: "Method to add a row on an array or block field",
},
{
drawerTitle: 'addFieldRow',
drawerDescription: 'A useful method to programmatically add a row to an array or block field.',
drawerSlug: 'addFieldRow',
drawerContent: (
<>
<TableWithDrawers
columns={[
'Prop',
'Description',
]}
rows={[
[
{
value: <strong><code>path</code></strong>,
},
{
value: "The path to the array or block field",
},
],
[
{
value: <strong><code>rowIndex</code></strong>,
},
{
value: "The index of the row to add. If omitted, the row will be added to the end of the array.",
},
],
[
{
value: <strong><code>data</code></strong>,
},
{
value: "The data to add to the row",
},
],
]}
/>
{' '}
<br />
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { addFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
addFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "text",
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
},
})
}}
>
Add Row
</button>
)
}`}
</pre>
<p>An example config to go along with the custom component</p>
<pre>
{`const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: CustomArrayManager,
},
},
},
],
}`}
</pre>
</>
)
}
],
[
{
value: <strong><code>removeFieldRow</code></strong>,
},
{
value: "Method to remove a row from an array or block field",
},
{
drawerTitle: 'removeFieldRow',
drawerDescription: 'A useful method to programmatically remove a row from an array or block field.',
drawerSlug: 'removeFieldRow',
drawerContent: (
<>
<TableWithDrawers
columns={[
'Prop',
'Description',
]}
rows={[
[
{
value: <strong><code>path</code></strong>,
},
{
value: "The path to the array or block field",
},
],
[
{
value: <strong><code>rowIndex</code></strong>,
},
{
value: "The index of the row to remove",
},
],
]}
/>
{' '}
<br />
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { removeFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
removeFieldRow({
path: "arrayField",
rowIndex: 0,
})
}}
>
Remove Row
</button>
)
}`}
</pre>
<p>An example config to go along with the custom component</p>
<pre>
{`const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: CustomArrayManager,
},
},
},
],
}`}
</pre>
</>
)
}
],
[
{
value: <strong><code>replaceFieldRow</code></strong>,
},
{
value: "Method to replace a row from an array or block field",
},
{
drawerTitle: 'replaceFieldRow',
drawerDescription: 'A useful method to programmatically replace a row from an array or block field.',
drawerSlug: 'replaceFieldRow',
drawerContent: (
<>
<TableWithDrawers
columns={[
'Prop',
'Description',
]}
rows={[
[
{
value: <strong><code>path</code></strong>,
},
{
value: "The path to the array or block field",
},
],
[
{
value: <strong><code>rowIndex</code></strong>,
},
{
value: "The index of the row to replace",
},
],
[
{
value: <strong><code>data</code></strong>,
},
{
value: "The data to replace within the row",
},
],
]}
/>
{' '}
<br />
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { replaceFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
replaceFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "updated text",
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
},
})
}}
>
Replace Row
</button>
)
}`}
</pre>
<p>An example config to go along with the custom component</p>
<pre>
{`const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: CustomArrayManager,
},
},
},
],
}`}
</pre>
</>
)
}
],
]}
/>
### useCollapsible
The `useCollapsible` hook allows you to control parent collapsibles:
| Property | Description |
|---------------------------|--------------------------------------------------------------------------------------------------------------------|
| **`collapsed`** | 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 |
| **`withinCollapsible`** | Determine when you are within another collaspible | |
**Example:**
```tsx
import React from 'react'
import { useCollapsible } from 'payload/components/utilities'
const CustomComponent: React.FC = () => {
const { collapsed, toggle } = useCollapsible()
return (
<div>
<p className="field-type">I am {collapsed ? 'closed' : 'open'}</p>
<button onClick={toggle} type="button">
Toggle
</button>
</div>
)
}
```
### useDocumentInfo
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
| 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 |
| **`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 |
| **`unpublishedVersions`** | Unpublished versions of the current doc |
| **`publishedDoc`** | The currently published version of the doc being edited |
| **`getVersions`** | Method to trigger the retrieval of document versions |
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create) |
| **`getDocPermissions`** | Method to trigger the retrieval of document level permissions |
**Example:**
```tsx
import { useDocumentInfo } from 'payload/components/utilities'
const LinkFromCategoryToPosts: React.FC = () => {
// highlight-start
const { id } = useDocumentInfo()
// highlight-end
// id will be undefined on the create form
if (!id) {
return null
}
return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}>
View posts
</a>
)
}
```
### 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:
```tsx
import { useLocale } from 'payload/components/utilities'
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale()
// highlight-end
const trans = {
en: 'Hello',
es: 'Hola',
}
return <span> {trans[locale.code]} </span>
}
```
### useAuth
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
| Property | Description |
| ------------------------ | --------------------------------------------------------------------------------------- |
| **`user`** | The currently logged in user |
| **`logOut`** | A method to log out the currently logged in user |
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
```tsx
import { useAuth } from 'payload/components/utilities'
import { User } from '../payload-types.ts'
const Greeting: React.FC = () => {
// highlight-start
const { user } = useAuth<User>()
// highlight-end
return <span>Hi, {user.email}!</span>
}
```
### useConfig
Used to easily fetch the full Payload config.
```tsx
import { useConfig } from 'payload/components/utilities'
const MyComponent: React.FC = () => {
// highlight-start
const config = useConfig()
// highlight-end
return <span>{config.serverURL}</span>
}
```
### useEditDepth
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'
const MyComponent: React.FC = () => {
// highlight-start
const editDepth = useEditDepth()
// highlight-end
return <span>My component is {editDepth} levels deep</span>
}
```
### usePreferences
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
### useTheme
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'
const MyComponent: React.FC = () => {
// highlight-start
const { autoMode, setTheme, theme } = useTheme()
// highlight-end
return (
<>
<span>The current theme is {theme} and autoMode is {autoMode}</span>
<button
type="button"
onClick={() => setTheme(prev => prev === "light" ? "dark" : "light")}
>
Toggle theme
</button>
</>
)
}
```
### useTableColumns
Returns methods to manipulate table columns
```tsx
import { useTableColumns } from 'payload/components/hooks'
const MyComponent: React.FC = () => {
// highlight-start
const { setActiveColumns } = useTableColumns()
const resetColumns = () => {
setActiveColumns(['id', 'createdAt', 'updatedAt'])
}
// highlight-end
return (
<button
type="button"
onClick={resetColumns}
>
Reset columns
</button>
)
}
```
### useDocumentEvents
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:
| Property | Description |
|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| **`mostRecentUpdate`** | An object containing the most recently updated document. It contains the `entitySlug`, `id` (if collection), and `updatedAt` properties |
| **`reportUpdate`** | A method used to report updates to documents. It accepts the same arguments as the `mostRecentUpdate` property. |
**Example:**
```tsx
import { useDocumentEvents } from 'payload/components/hooks'
const ListenForUpdates: React.FC = () => {
const { mostRecentUpdate } = useDocumentEvents()
return (
<span>
{JSON.stringify(mostRecentUpdate)}
</span>
)
}
```
<Banner type="info">
Right now the `useDocumentEvents` hook only tracks recently updated documents, but in the future it will track more document-related events as needed, such as document creation, deletion, etc.
</Banner>

View File

@@ -1,91 +0,0 @@
---
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.
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload dynamically generates a beautiful, fully functional React admin panel to manage your data. It's extremely powerful and can be customized / extended upon easily by swapping in your own React components. You can add additional views, modify how built-in views look / work, swap out Payload branding for your client's, build your own field types and much more.
The Payload Admin panel can be bundled with our officially supported [Vite](/docs/admin/vite) and [webpack](/docs/admin/webpack) bundlers or you can integrate another bundler following our adapter pattern approach.
When bundled, it is code-split, highly performant (even with 100+ fields), and written fully in TypeScript.
<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.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/admin.jpg"
srcDark="https://payloadcms.com/images/docs/admin-dark.jpg"
alt="Admin panel with collapsible sidebar"
caption="Redesigned admin panel with a collapsible sidebar that's open by default, providing greater extensibility and enhanced horizontal real estate."
/>
## Admin Options
All options for the Admin panel are defined in your base Payload config file.
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bundler` | The bundler that you would like to use to bundle the admin panel. Officially supported bundlers: [Webpack](/docs/admin/webpack) and [Vite](/docs/admin/vite). |
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
| `buildPath` | Specify an absolute path for where to store the built Admin panel 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`, `ogImage`, and `favicon`. |
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
| `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. |
| `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) |
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
| `vite` | Customize the Vite config that's used to generate the Admin panel. [More](/docs/admin/vite) |
| `logoutRoute` | The route for the `logout` page. |
| `inactivityRoute` | The route for the `logout` inactivity page. |
### The Admin User Collection
<Banner type="warning">
<strong>Important:</strong>
<br />
The Payload Admin panel can only be used by one Collection that supports
[Authentication](/docs/authentication/overview).
</Banner>
To specify which Collection to use to log in to the Admin panel, pass the `admin` options a `user` key equal to the slug of the Collection that you'd like to use.
`payload.config.js`:
```ts
import { buildConfig } from 'payload/config'
const config = buildConfig({
admin: {
user: 'admins', // highlight-line
},
})
```
By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version.
**Note: you can use whatever Collection you'd like to access the Admin panel as long as the Collection supports Authentication. 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 Payload Admin panel will use `admins`. Any users logged in as `customers` will not be able to log in via the Admin panel.
### Light and dark modes
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.
### Restricting user access
If you would like to restrict which users from a single Collection can access the Admin panel, you can use the `admin` access control function. [Click here](/docs/access-control/overview#admin) to learn more.

View File

@@ -1,159 +0,0 @@
---
title: Managing User Preferences
label: Preferences
order: 50
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, express
---
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.
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
<Banner type="warning">
<strong>Important:</strong>
<br />
All preferences are stored on an individual user basis. Payload automatically recognizes the user
that is reading or setting a preference via all provided authentication methods.
</Banner>
### 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:
- 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
- You might want to store `recentlyAccessed` documents to give admin editors an easy shortcut back to their recently accessed documents on the `Dashboard` or similar
- Many other use cases exist. Invent your own! Give your editors an intelligent and persistent editing experience.
### 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:
| 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. |
| `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. |
### APIs
Preferences are available to both [GraphQL](/docs/graphql/overview#preferences) and [REST](/docs/rest-api/overview#) APIs.
### 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:
##### `getPreference`
This async method provides an easy way to retrieve a user's preferences by `key`. It will return a promise containing the resulting preference value.
**Arguments**
- `key`: the `key` of your preference to retrieve.
##### `setPreference`
Also async, this method provides you with an easy way to set a user preference. It returns `void`.
**Arguments:**
- `key`: the `key` of your preference to set.
- `value`: the `value` of your preference that you're looking to set.
## 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.
```
import React, { Fragment, useState, useEffect, useCallback } from 'react';
import { usePreferences } from 'payload/components/preferences';
const lastUsedColorsPreferenceKey = 'last-used-colors';
const CustomComponent = (props) => {
const { getPreference, setPreference } = usePreferences();
// Store the last used colors in local state
const [lastUsedColors, setLastUsedColors] = useState([]);
// Callback to add a color to the last used colors
const updateLastUsedColors = useCallback((color) => {
// First, check if color already exists in last used colors.
// If it already exists, there is no need to update preferences
const colorAlreadyExists = lastUsedColors.indexOf(color) > -1;
if (!colorAlreadyExists) {
const newLastUsedColors = [
...lastUsedColors,
color,
];
setLastUsedColors(newLastUsedColors);
setPreference(lastUsedColorsPreferenceKey, newLastUsedColors);
}
}, [lastUsedColors, setPreference]);
// Retrieve preferences on component mount
// This will only be run one time, because the `getPreference` method never changes
useEffect(() => {
const asyncGetPreference = async () => {
const lastUsedColorsFromPreferences = await getPreference(lastUsedColorsPreferenceKey);
setLastUsedColors(lastUsedColorsFromPreferences);
};
asyncGetPreference();
}, [getPreference]);
return (
<div>
<button
type="button"
onClick={() => updateLastUsedColors('red')}
>
Use red
</button>
<button
type="button"
onClick={() => updateLastUsedColors('blue')}
>
Use blue
</button>
<button
type="button"
onClick={() => updateLastUsedColors('purple')}
>
Use purple
</button>
<button
type="button"
onClick={() => updateLastUsedColors('yellow')}
>
Use yellow
</button>
{lastUsedColors && (
<Fragment>
<h5>Last used colors:</h5>
<ul>
{lastUsedColors?.map((color) => (
<li key={color}>
{color}
</li>
))}
</ul>
</Fragment>
)}
</div>
);
};
export default CustomComponent;
```

View File

@@ -1,161 +0,0 @@
---
title: Vite
label: Vite
order: 90
desc: NEEDS TO BE WRITTEN
---
<Banner type="info">
The Vite bundler is currently in beta. If you would like to help us test this package, we'd love to hear from you if you find any [bugs or issues](https://github.com/payloadcms/payload/issues/)!
</Banner>
Payload has a Vite bundler that you can install and bundle the Admin Panel with. This is an alternative to the [Webpack](/docs/admin/webpack) bundler and might give some performance boosts to your development workflow.
To use Vite as your bundler, first you need to install the package:
```bash
yarn add @payloadcms/bundler-vite
```
Then you will need to add the [bundler](/docs/admin/bundlers) to your Payload config:
```ts
import { buildConfig } from '@payloadcms/config'
import { viteBundler } from '@payloadcms/bundler-vite'
export default buildConfig({
collections: [],
admin: {
bundler: viteBundler(),
}
})
```
Vite works fundamentally differently than Webpack. In development mode, it will first pre-bundle any of your dependencies that are CommonJS-only, and then it'll leverage ESM directly in your browser for a better HMR experience.
It then uses Rollup to create production builds of your admin UI. With Vite, you should see a decent performance boost—especially after your first cold start. However, that first cold start might take a few more seconds.
<Banner type="warning">
In most cases, Vite should work out of the box. But existing Payload plugins may need to make compatibility changes to support Vite.
</Banner>
This is because Vite aliases work fundamentally differently than Webpack aliases, and Payload relies on aliasing server-only code out of the Payload config to ensure that the bundled admin JS works within your browser.
Here are the main differences between how Vite aliases work and how Webpack aliases work.
**Vite aliases do not work with absolute paths.**
In Vite, alias keys must <strong>exactly match</strong> a import paths. If you have 2 files that import the same server-only module, but have different import paths, you would need to add 2 aliases to support both import paths.
```ts
// File A
import serverOnlyModule from '../server-only-module'
// File B
import serverOnlyModule from '../../server-only-module'
// payload.config.ts
// You would need to add 2 aliases to support both import paths
export const buildConfig({
collections: [],
admin: {
bundler: viteBundler(),
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
// Add your own aliases using the find and replacement keys
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
find: '../../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
}
})
```
**Vite aliases do not get applied to pre-bundled dependencies.**
This especially affects plugins, as plugins will be pre-bundled by Vite using `esbuild`. To get around this and support Vite, plugin authors need to configure an alias to their plugin at the top level, so that the alias will work accordingly.
Here's an example. Say your plugin is called `payload-plugin-cool`. It's imported as follows:
```ts
import { myCoolPlugin } from 'payload-plugin-cool'
```
That plugin should create an alias to support Vite as follows:
```ts
{
// aliases go here
find: 'payload-plugin-cool',
replacement: path.resolve(__dirname, './my-admin-plugin.js')
}
```
This will effectively alias the entire plugin and work with Vite. If the plugin requires admin-specific code, then the `./my-admin-plugin.js` alias target file should reflect any changes necessary to the admin UI that the main server-side plugin performs.
### Extending the Vite config
The Payload config supports a new property for plugins to be able to extend the Vite config specifically. That property exists on the main Payload config under `admin.vite`. You can check out the [Vite docs](https://vitejs.dev/config/shared-options.html) for more information on what you can do with the Vite config.
It's a function that takes a Vite config, and returns an updated Vite config. Here's an example:
```ts
export const buildConfig({
collections: [],
admin: {
bundler: viteBundler(),
vite: (incomingViteConfig) => {
const existingAliases = incomingViteConfig?.resolve?.alias || {};
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
// Pass the existing Vite aliases
if (Array.isArray(existingAliases)) {
aliasArray = existingAliases;
} else {
aliasArray = Object.values(existingAliases);
}
// Add your own aliases using the find and replacement keys
aliasArray.push({
find: '../server-only-module',
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
});
return {
...incomingViteConfig,
resolve: {
...(incomingViteConfig?.resolve || {}),
alias: aliasArray,
}
};
},
}
})
```
Learn more about [aliasing server-only modules](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
Even though there is a new property for Vite configs specifically, we have implemented some "compatibility" between Webpack and Vite out-of-the-box.
If your config specifies Webpack aliases, we attempt to leverage them automatically within the Vite config. They are merged into the Vite alias configuration seamlessly and may work out-of-the-box.

View File

@@ -1,67 +0,0 @@
---
title: Webpack
label: Webpack
order: 80
desc: The Payload admin panel uses Webpack 5 and supports many common functionalities such as SCSS and Typescript out of the box to give you more freedom.
keywords: admin, webpack, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload has a Webpack (v5) bundler that you can build the Admin panel with. For now, we recommended using it because it is stable. If you are feeling a bit more adventurous you can give the [Vite](/docs/admin/vite) bundler a shot.
Out of the box, the Webpack bundler supports common functionalities such as SCSS and Typescript, but there are many cases where you may need to add support for additional functionalities.
#### Installation
```bash
yarn add @payloadcms/bundler-webpack
```
#### Import the bundler
```ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { webpackBundler } from '@payloadcms/bundler-webpack'
export default buildConfig({
// highlight-start
admin: {
bundler: webpackBundler()
},
// highlight-end
})
```
### Extending Webpack
If you need to extend the Webpack config, you can do so by passing a function to the `admin.webpack` property on your Payload config.
The function will receive the Webpack config as an argument and should return the modified config.
```ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { webpackBundler } from '@payloadcms/bundler-webpack'
export default buildConfig({
admin: {
bundler: webpackBundler()
// highlight-start
webpack: (config) => {
// full control of the Webpack config
return config
},
// highlight-end
},
})
```
<Banner type="success">
<strong>Tip:</strong>
<br />
If changes to your Webpack aliases are not surfacing, they might be
[cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try
deleting that folder and restarting your server.
</Banner>

View File

@@ -1,292 +0,0 @@
---
title: Authentication Config
label: Config
order: 20
desc: Enable and customize options in the Authentication config for features including Forgot Password, Login Attempts, API key usage and more.
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload's Authentication is extremely powerful and gives you everything you need when you go to build a new app or site in a secure and responsible manner.
To enable Authentication on a collection, define an `auth` property and set it to either `true` or to an object containing the options below.
## Options
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More](/docs/authentication/config#api-keys) |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the express `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More](/docs/authentication/config#forgot-password) |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More](/docs/authentication/config#email-verification) |
| **`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:
**`generateEmailHTML`**
Function that accepts one argument, containing `{ req, token, user }`, that allows for overriding the HTML within emails that are sent to users attempting to reset their password. The function should return a string that supports HTML, which can be a full HTML email.
<Banner type="success">
<strong>Tip:</strong>
<br />
HTML templating can be used to create custom email templates, inline CSS automatically, and more.
You can make a reusable function that standardizes all email sent from Payload, which makes
sending custom emails more DRY. Payload doesn't ship with an HTML templating engine, so you are
free to choose your own.
</Banner>
Example:
```ts
import { CollectionConfig } from 'payload/types'
export const Customers: CollectionConfig = {
slug: 'customers',
auth: {
forgotPassword: {
// highlight-start
generateEmailHTML: ({ req, token, user }) => {
// Use the token provided to allow your user to reset their password
const resetPasswordURL = `https://yourfrontend.com/reset-password?token=${token}`
return `
<!doctype html>
<html>
<body>
<h1>Here is my custom email template!</h1>
<p>Hello, ${user.email}!</p>
<p>Click below to reset your password.</p>
<p>
<a href="${resetPasswordURL}">${resetPasswordURL}</a>
</p>
</body>
</html>
`
},
// highlight-end
},
},
}
```
<Banner type="warning">
<strong>Important:</strong>
<br />
If you specify a different URL to send your users to for resetting their password, such as a page
on the frontend of your app or similar, you need to handle making the call to the Payload REST or
GraphQL reset-password operation yourself on your frontend, using the token that was provided for
you. Above, it was passed via query parameter.
</Banner>
**`generateEmailSubject`**
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Example:
```ts
{
slug: 'customers',
auth: {
forgotPassword: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
// highlight-end
}
}
}
```
### Email Verification
If you'd like to require email verification before a user can successfully log in, you can enable it by passing `true` or an `options` object to `auth.verify`. The following options are available:
**`generateEmailHTML`**
Function that accepts one argument, containing `{ req, token, user }`, that allows for overriding the HTML within emails that are sent to users indicating how to validate their account. The function should return a string that supports HTML, which can optionally be a full HTML email.
Example:
```ts
import { CollectionConfig } from 'payload/types'
export const Customers: CollectionConfig = {
slug: 'customers',
auth: {
verify: {
// highlight-start
generateEmailHTML: ({ req, token, user }) => {
// Use the token provided to allow your user to verify their account
const url = `https://yourfrontend.com/verify?token=${token}`
return `Hey ${user.email}, verify your email by clicking here: ${url}`
},
// highlight-end
},
},
}
```
<Banner type="warning">
<strong>Important:</strong>
<br />
If you specify a different URL to send your users to for email verification, such as a page on the
frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL
verification operation yourself on your frontend, using the token that was provided for you.
Above, it was passed via query parameter.
</Banner>
**`generateEmailSubject`**
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Example:
```ts
{
slug: 'customers',
auth: {
verify: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
// highlight-end
}
}
}
```
### 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.
The `admin.autologin` property is used to configure the how visitors are handled when accessing the admin panel.
The default is that all users will have to login and this should not be enabled for environments where data needs to protected.
#### autoLogin Options
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| **`email`** | The email address of the user to login as |
| **`password`** | The password of the user to login as |
| **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. |
The recommended way to use this feature is behind an environment variable to ensure it is disabled when in production.
**Example:**
```ts
export default buildConfig({
admin: {
user: 'users',
// highlight-start
autoLogin:
process.env.PAYLOAD_PUBLIC_ENABLE_AUTOLOGIN === 'true'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
// highlight-end
},
collections: [
/** */
],
})
```

View File

@@ -1,404 +0,0 @@
---
title: Authentication Operations
label: Operations
order: 30
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, express
---
Enabling Authentication on a Collection automatically exposes additional auth-based operations in the Local, REST, and GraphQL APIs.
### Access
The Access operation returns what a logged in user can and can't do with the collections and globals that are registered via your config. This data can be immensely helpful if your app needs to show and hide certain features based on access control, as the Payload Admin panel does.
**REST API endpoint**:
`GET http://localhost:3000/api/access`
Example response:
```ts
{
canAccessAdmin: true,
collections: {
pages: {
create: {
permission: true,
},
read: {
permission: true,
},
update: {
permission: true,
},
delete: {
permission: true,
},
fields: {
title: {
create: {
permission: true,
},
read: {
permission: true,
},
update: {
permission: true,
},
}
}
}
}
}
```
**Example GraphQL Query**:
```graphql
query {
Access {
pages {
read {
permission
}
}
}
}
```
Document access can also be queried on a collection/global basis. Access on a global can queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
### Me
Returns either a logged in user with token or null when there is no logged in user.
**REST API endpoint**:
`GET http://localhost:3000/api/[collection-slug]/me`
Example response:
```ts
{
user: { // The JWT "payload" ;) from the logged in user
email: 'dev@payloadcms.com',
createdAt: "2020-12-27T21:16:45.645Z",
updatedAt: "2021-01-02T18:37:41.588Z",
id: "5ae8f9bde69e394e717c8832"
},
token: '34o4345324...', // The token that can be used to authenticate the user
exp: 1609619861, // Unix timestamp representing when the user's token will expire
}
```
**Example GraphQL Query**:
```graphql
query {
me[collection-singular-label] {
user {
email
}
exp
}
}
```
### Login
Accepts an `email` and `password`. On success, it will return the logged in user as well as a token that can be used to authenticate. In the GraphQL and REST APIs, this operation also automatically sets an HTTP-only cookie including the user's token. If you pass an Express `res` to the Local API operation, Payload will set a cookie there as well.
**Example REST API login**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
password: 'this-is-not-our-password...or-is-it?',
}),
})
const json = await res.json()
// JSON will be equal to the following:
/*
{
user: {
email: 'dev@payloadcms.com',
createdAt: "2020-12-27T21:16:45.645Z",
updatedAt: "2021-01-02T18:37:41.588Z",
id: "5ae8f9bde69e394e717c8832"
},
token: '34o4345324...',
exp: 1609619861
}
*/
```
**Example GraphQL Mutation**:
```graphql
mutation {
login[collection-singular-label](email: "dev@payloadcms.com", password: "yikes") {
user {
email
}
exp
token
}
}
```
**Example Local API login**:
```ts
const result = await payload.login({
collection: '[collection-slug]',
data: {
email: 'dev@payloadcms.com',
password: 'get-out',
},
})
```
### Logout
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
**Example REST API logout**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
```
**Example GraphQL Mutation**:
```
mutation {
logout[collection-singular-label]
}
```
### Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
This operation requires a non-expired token to send back a new one. If the user's token has already expired, you will need to allow them to log in again to retrieve a new token.
If successful, this operation will automatically renew the user's HTTP-only cookie and will send back the updated token in JSON.
**Example REST API token refresh**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
const json = await res.json()
// JSON will be equal to the following:
/*
{
user: {
email: 'dev@payloadcms.com',
createdAt: "2020-12-27T21:16:45.645Z",
updatedAt: "2021-01-02T18:37:41.588Z",
id: "5ae8f9bde69e394e717c8832"
},
refreshedToken: '34o4345324...',
exp: 1609619861
}
*/
```
**Example GraphQL Mutation**:
```
mutation {
refreshToken[collection-singular-label] {
user {
email
}
refreshedToken
}
}
```
### Verify by Email
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
**Example REST API user verification**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
```
**Example GraphQL Mutation**:
```graphql
mutation {
verifyEmail[collection-singular-label](token: "TOKEN_HERE")
}
```
**Example Local API verification**:
```ts
const result = await payload.verifyEmail({
collection: '[collection-slug]',
token: 'TOKEN_HERE',
})
```
### Unlock
If a user locks themselves out and you wish to deliberately unlock them, you can utilize the Unlock operation. The Admin panel features an Unlock control automatically for all collections that feature max login attempts, but you can programmatically unlock users as well by using the Unlock operation.
To restrict who is allowed to unlock users, you can utilize the [`unlock`](/docs/access-control/overview#unlock) access control function.
**Example REST API unlock**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/unlock`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
})
```
**Example GraphQL Mutation**:
```
mutation {
unlock[collection-singular-label](email: "dev@payloadcms.com")
}
```
**Example Local API unlock**:
```ts
const result = await payload.unlock({
collection: '[collection-slug]',
data: {
email: 'dev@payloadcms.com',
},
})
```
### Forgot Password
Payload comes with built-in forgot password functionality. Submitting an email address to the Forgot Password operation will generate an email and send it to the respective email address with a link to reset their password.
The link to reset the user's password contains a token which is what allows the user to securely reset their password.
By default, the Forgot Password operations send users to the Payload Admin panel to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/config#forgot-password).
**Example REST API Forgot Password**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
})
```
**Example GraphQL Mutation**:
```
mutation {
forgotPassword[collection-singular-label](email: "dev@payloadcms.com")
}
```
**Example Local API forgot password**:
```ts
const token = await payload.forgotPassword({
collection: '[collection-slug]',
data: {
email: 'dev@payloadcms.com',
},
disableEmail: false, // you can disable the auto-generation of email via local API
})
```
<Banner type="success">
<strong>Tip:</strong>
<br />
You can stop the reset-password email from being sent via using the local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
</Banner>
### Reset Password
After a user has "forgotten" their password and a token is generated, that token can be used to send to the reset password operation along with a new password which will allow the user to reset their password securely.
**Example REST API Reset Password**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: 'TOKEN_GOES_HERE'
password: 'not-today',
}),
});
const json = await res.json();
// JSON will be equal to the following:
/*
{
user: {
email: 'dev@payloadcms.com',
createdAt: "2020-12-27T21:16:45.645Z",
updatedAt: "2021-01-02T18:37:41.588Z",
id: "5ae8f9bde69e394e717c8832"
},
token: '34o4345324...',
exp: 1609619861
}
*/
```
**Example GraphQL Mutation**:
```graphql
mutation {
resetPassword[collection-singular-label](token: "TOKEN_GOES_HERE", password: "not-today")
}
```

View File

@@ -1,181 +0,0 @@
---
title: Authentication Overview
label: Overview
order: 10
desc: Payload provides highly secure user Authentication out of the box, and you can fully customize, override, or remove the default Authentication support.
keywords: authentication, config, configuration, overview, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<YouTube
id="CT4KafeJjTI"
title="Simplified Authentication for Headless CMS: Unlocking Reusability in One Line"
/>
<Banner>
Payload provides for highly secure and customizable user Authentication out of the box, which
allows for users to identify themselves to Payload.
</Banner>
Authentication is used within the Payload Admin panel itself as well as throughout your app(s) themselves however you determine necessary.
![Authentication admin panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg)
_Admin panel screenshot depicting an Admins Collection with Auth enabled_
**Here are some common use cases of Authentication outside of Payload's dashboard itself:**
- Customer accounts for an ecommerce app
- Customer accounts for a SaaS product
- P2P app or social site where users need to log in and manage their profiles
- Online game where players need to track their progress over time
By default, Payload provides you with a `User` collection that supports Authentication, which is used to access the Admin panel. But, you can add support to one or many Collections of your own. For more information on how to customize, override, or remove the default `User` collection, [click here](/docs/admin/overview#the-admin-user-collection).
### Enabling Auth on a collection
Every Payload Collection can opt-in to supporting Authentication by specifying the `auth` property on the Collection's config to either `true` or to an object containing `auth` options.
**For a full list of all `auth` options, [click here](/docs/authentication/config).**
Simple example collection:
```ts
import { CollectionConfig } from 'payload/types'
export const Admins: CollectionConfig = {
slug: 'admins',
// highlight-start
auth: {
tokenExpiration: 7200, // How many seconds to keep the user logged in
verify: true, // Require email verification before being allowed to authenticate
maxLoginAttempts: 5, // Automatically lock a user out after X amount of failed logins
lockTime: 600 * 1000, // Time period to allow the max login attempts
// More options are available
},
// highlight-end
fields: [
{
name: 'role',
type: 'select',
required: true,
options: [
'user',
'admin',
'editor',
'developer',
],
},
],
}
```
**By enabling Authentication on a config, the following modifications will automatically be made to your Collection:**
1. `email` as well as password `salt` & `hash` fields will be added to your Collection's schema
1. The Admin panel will feature a new set of corresponding UI to allow for changing password and editing email
1. [A new set of `operations`](/docs/authentication/operations) will be exposed via Payload's REST, Local, and GraphQL APIs
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.
### 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 Express `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 Express{' '}
<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.

View File

@@ -1,56 +0,0 @@
---
title: Using the Payload Auth Middleware
label: Using the Middleware
order: 40
desc: Make full use of Payload's built-in authentication with your own custom Express endpoints by adding Payload's authentication middleware.
keywords: authentication, middleware, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Because Payload uses your existing Express server, you are free to add whatever logic you need to your app through endpoints of your own. However, Payload does not add its middleware to your Express app itself—instead, it scopes all of its middleware to Payload-specific routers.
This approach has a ton of benefits - it's great for isolation of concerns and limiting scope, but it also means that your additional routes won't have access to Payload's user authentication.
<Banner type="success">
You can make full use of Payload's built-in authentication within your own custom Express
endpoints by adding Payload's authentication middleware.
</Banner>
<Banner type="warning">
Payload must be initialized before the `payload.authenticate` middleware can be used. This is done
by calling `payload.init()` prior to adding the middleware.
</Banner>
Example in `server.js`:
```ts
import express from 'express'
import payload from 'payload'
const app = express()
const start = async () => {
await payload.init({
secret: 'PAYLOAD_SECRET_KEY',
express: app,
})
const router = express.Router()
// Note: Payload must be initialized before the `payload.authenticate` middleware can be used
router.use(payload.authenticate) // highlight-line
router.get('/', (req, res) => {
if (req.user) {
return res.send(`Authenticated successfully as ${req.user.email}.`)
}
return res.send('Not authenticated')
})
app.use('/some-route-here', router)
app.listen(3000)
}
start()
```

View File

@@ -1,61 +0,0 @@
---
title: Project Configuration
label: Configuration
order: 20
desc: Quickly configure and deploy your Payload Cloud project in a few simple steps.
keywords: configuration, config, settings, project, cloud, payload cloud, deploy, deployment
---
### Select your plan
Once you have created a project, you will need to select your plan. This will determine the resources that are allocated to your project and the features that are available to you.
<Banner type="success">
Note: All Payload Cloud teams that deploy a project require a card on file. This helps us prevent
fraud and abuse on our platform. If you select a plan with a free trial, you will not be charged
until your trial period is over. Well remind you 7 days before your trial ends and you can cancel
anytime.
</Banner>
### Project Details
| Option | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Region** | Select the region closest to your audience. This will ensure the fastest communication between your data and your client. |
| **Project Name** | A name for your project. You can change this at any time. |
| **Project Slug** | Choose a unique slug to identify your project. This needs to be unique for your team and you can change it any time. |
| **Team** | Select the team you want to create the project under. If this is your first project, a personal team will be created for you automatically. You can modify your team settings and invite new members at any time from the Team Settings page. |
### Build Settings
If you are deploying a new project from a template, the following settings will be automatically configured for you. If you are using your own repository, you need to make sure your build settings are accurate for your project to deploy correctly.
| Option | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Root Directory** | The folder where your `package.json` file lives. |
| **Install Command** | The command used to install your modules, for example: `yarn install` or `npm install` |
| **Build Command** | The command used to build your application, for example: `yarn build` or `npm run build` |
| **Serve Command** | The command used to serve your application, for example: `yarn serve` or `npm run serve` |
| **Branch to Deploy** | Select the branch of your repository that you want to deploy from. This is the branch that will be used to build your project when you commit new changes. |
| **Default Domain** | Set a default domain for your project. This must be unique and you will not able to change it. You can always add a custom domain later in your project settings. |
### Environment Variables
Any of the features in Payload Cloud that require environment variables will automatically be provided to your application. If your app requires any custom environment variables, you can set them here.
<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
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
</Banner>
### Payment
Payment methods can be set per project and can be updated any time. You can use teams default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings.
<Banner type="success">
<strong>Note:</strong> All Payload Cloud teams that deploy a project require a card on file. This
helps us prevent fraud and abuse on our platform. If you select a plan with a free trial, you will
not be charged until your trial period is over. Well remind you 7 days before your trial ends and
you can cancel anytime.
</Banner>

View File

@@ -1,51 +0,0 @@
---
title: Getting Started
label: Getting Started
order: 10
desc: Get started with Payload Cloud, a deployment solution specifically designed for Node + MongoDB applications.
keywords: cloud, hosted, database, storage, email, deployment, serverless, node, mongodb, s3, aws, cloudflare, atlas, resend, payload, cms
---
A deployment solution specifically designed for Node.js + MongoDB applications, offering seamless deployment of your entire stack in one place. You can get started in minutes with a one-click template or bring your own codebase with you.
Payload Cloud offers various plans tailored to meet your specific needs, including a MongoDB Atlas database, S3 file storage, and email delivery powered by [Resend](https://resend.com). To see a full breakdown of features and plans, see our [Cloud Pricing page](https://payloadcms.com/cloud-pricing).
To get started, you first need to create an account. Head over to [the login screen](https://payloadcms.com/login) and **Register for Free**.
<Banner type="success">
To create your first project, you can either select [a template](#starting-from-a-template) or
[import an existing project](#importing-from-an-existing-codebase) from GitHub.
</Banner>
## Starting from a Template
Templates come preconfigured and provide a one-click solution to quickly deploy a new application.
![Screen for creating a new project from a template](https://payloadcms.com/images/docs/cloud/create-from-template.jpg)
_Creating a new project from a template._
After creating an account, select your desired template from the Projects page. At this point, you need to connect to authorize the Payload Cloud application with your GitHub account. Click Continue with GitHub and follow the prompts to authorize the app.
Next, select your `GitHub Scope`. If you belong to multiple organizations, they will show up here. If you do not see the organization you are looking for, you may need to adjust your GitHub app permissions.
After selecting your scope, create a unique `repository name` and select whether you want your repository to be public or private on GitHub.
<Banner type="warning">
<strong>Note:</strong> Public repositories can be accessed by anyone online, while private
repositories grant access only to you and anyone you explicitly authorize.
</Banner>
Once you are ready, click **Create Project**. This will clone the selected template to a new repository in your GitHub account, and take you to the configuration page to set up your project for deployment.
## Importing from an Existing Codebase
Payload Cloud works for any Node.js + MongoDB app. From the New Project page, select **import an existing Git codebase**. Choose the organization and select the repository you want to import. From here, you will be taken to the configuration page to set up your project for deployment.
![Screen for creating a new project from an existing repository](https://payloadcms.com/images/docs/cloud/create-from-existing.jpg)
_Creating a new project from an existing repository._
<Banner type="warning">
<strong>Note:</strong> In order to make use of the features of Payload Cloud in your own codebase,
you will need to add the [Cloud Plugin](https://github.com/payloadcms/plugin-cloud) to your
Payload app.
</Banner>

View File

@@ -1,129 +0,0 @@
---
title: Cloud Projects
label: Projects
order: 40
desc: Manage your Payload Cloud projects.
keywords: cloud, payload cloud, projects, project, overview, database, file storage, build settings, environment variables, custom domains, email, developing locally
---
### Overview
<Banner>
The overview tab shows your most recent deployment, along with build and deployment logs. From
here, you can see your live URL, deployment details like timestamps and commit hash, as well as
the status of your deployment. You can also trigger a redeployment manually, which will rebuild
your project using the current configuration.
</Banner>
![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg)
_A screenshot of the Overview page for a Cloud project._
### Database
Your Payload Cloud project comes with a MongoDB serverless Atlas DB instance or a Dedicated Atlas cluster, depending on your plan. To interact with your cloud database, you will be provided with a MongoDB connection string. This can be found under the **Database** tab of your project.
`mongodb+srv://your_connection_string`
### File Storage
Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this plugin extends Payload so that all of your media will be stored in S3 rather than locally.
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/plugin-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
#### Accessing Files Outside of Payload Cloud
If you'd like to access your files outside of Payload Cloud, you'll need to retrieve some values from your project's settings and put them into your environment variables. In Payload Cloud, navigate to the File Storage tab and copy the values using the copy button. Put these values in your .env file. Also copy the Cognito Password value separately and put into your .env file as well.
When you are done, you should have the following values in your .env file:
```env
PAYLOAD_CLOUD=true
PAYLOAD_CLOUD_ENVIRONMENT=prod
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
PAYLOAD_CLOUD_PROJECT_ID=
PAYLOAD_CLOUD_BUCKET=
PAYLOAD_CLOUD_BUCKET_REGION=
PAYLOAD_CLOUD_COGNITO_PASSWORD=
```
The plugin will pick up these values and use them to access your files.
### Build Settings
You can update settings from your Projects Settings tab. Changes to your build settings will trigger a redeployment of your project.
### Environment Variables
From the Environment Variables page of the Settings tab, you can add, update and delete variables for use in your project. Like build settings, these changes will trigger a redeployment of your project.
<Banner>
Note: For security reasons, any variables you wish to provide to the Admin panel must be prefixed
with `PAYLOAD_PUBLIC_`.  Learn more
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
</Banner>
### Custom Domains
With Payload Cloud, you can add custom domain names to your project. To do so, first go to the Domains page of the Settings tab of your project. Here you can see your default domain. To add a new domain, type in the domain name you wish to use.
<Banner>
Note: do not include the protocol (http:// or https://) or any paths (/page). Only include the
domain name and extension, and optionally a subdomain. - your-domain.com - backend.your-domain.com
</Banner>
Once you click save, a DNS record will be generated for your domain name to point to your live project. Add this record into your DNS providers records, and once the records are resolving properly (this can take 1hr to 48hrs in some cases), your domain will now to point to your live project.
You will also need to configure your Payload project to use your specified domain. In your `payload.config.ts` file, specify your `serverURL` with your domain:
```ts
export default buildConfig({
serverURL: 'https://example.com',
// the rest of your config,
})
```
### Email
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](https://payloadcms.com/docs/email/overview) overview.
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your projects Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.
### Developing Locally
To make changes to your project, you will need to clone the repository defined in your project settings to your local machine. In order to run your project locally, you will need configure your local environment first. Refer to your repositorys `README.md` file to see the steps needed for your specific template.
From there, you are ready to make updates to your project. When you are ready to make your changes live, commit your changes to the branch you specified in your Project settings, and your application will automatically trigger a redeploy and build from your latest commit.
### Cloud Plugin
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload config:
`yarn add @payloadcms/plugin-cloud`
```js
import { payloadCloud } from '@payloadcms/plugin-cloud'
import { buildConfig } from 'payload/config'
export default buildConfig({
plugins: [payloadCloud()],
// rest of config
})
```
<Banner type="warning">
**Note:** If your Payload config already has an email with transport, this will take precedence
over Payload Cloud's email service.
</Banner>
##### **Optional configuration**
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.
```js
payloadCloud({
storage: false, // Disable file storage
email: false, // Disable email delivery
})
```

View File

@@ -1,35 +0,0 @@
---
title: Cloud Teams
label: Teams
order: 30
desc: Manage your Payload Cloud team and billing settings.
keywords: team, teams, billing, subscription, payment, plan, plans, cloud, payload cloud
---
<Banner>
Within Payload Cloud, the team management feature offers you the ability to manage your
organization, team members, billing, and subscription settings.
</Banner>
![Payload Cloud Team Settings](https://payloadcms.com/images/docs/cloud/team-settings.jpg)
_A screenshot of the Team Settings page._
### Members
Each team has members that can interact with your projects. You can invite multiple people to your team and each individual can belong to more than one team. You can assign them either `owner` or `user` permissions. Owners are able to make admin-only changes, such as deleting projects, and editing billing information.
### Adding Members
To add a new member to your team, visit your Teams Settings page, and click “Invite Teammate”. You can then add their email address, and assign their role. Press “Save” to send the invitations, which will send an email to the invited team member where they can create a new account.
### Billing
Users can update billing settings and subscriptions for any teams where they are designated as an `owner`. To make updates to the teams payment methods, visit the Billing page under the Team Settings tab. You can add new cards, delete cards, and set a payment method as a default. The default payment method will be used in the event that another payment method fails.
### Subscriptions
From the Subscriptions page, a team owner can see all current plans for their team. From here, you can see the price of each plan, if there is an active trial, and when you will be billed next.
### Invoices
The Invoices page will you show you the invoices for your account, as well as the status on their payment.

View File

@@ -1,236 +0,0 @@
---
title: Collection Configs
label: Collections
order: 20
desc: Structure your Collections for your needs by defining fields, adding slugs and labels, establishing access control, tying in hooks, setting timestamps and more.
keywords: collections, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
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.
## Options
| Option | Description |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined. |
| **`db`** | Set custom database operations for this Collection. [More](/docs/database/overview#collection-operations) |
_\* An asterisk denotes that a property is required._
#### Simple collection example
```ts
import { CollectionConfig } from 'payload/types'
export const Orders: CollectionConfig = {
slug: 'orders',
fields: [
{
name: 'total',
type: 'number',
required: true,
},
{
name: 'placedBy',
type: 'relationship',
relationTo: 'customers',
required: true,
},
],
}
```
#### More collection config examples
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.
### Admin options
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin`
property on a collection's config.
| Option | Description |
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `group` | Text used as a label for grouping collection and global links together in the navigation. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
| `enableRichTextLink` | The [Rich Text](/docs/fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
### Preview
Collection `admin` options can accept a `preview` function that will be used to generate a link pointing to the frontend
of your app to preview data.
If the function is specified, a Preview button will automatically appear in the corresponding collection's Edit view.
Clicking the Preview button will link to the URL that is generated by the function.
**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.
**Example collection with preview function:**
```ts
import { CollectionConfig } from 'payload/types'
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'slug',
type: 'text',
required: true,
},
],
admin: {
preview: (doc, { locale }) => {
if (doc?.slug) {
return `https://bigbird.com/preview/posts/${doc.slug}?locale=${locale}`
}
return null
},
},
}
```
### Pagination
Here are a few options that you can specify options for pagination on a collection-by-collection basis:
| Option | Description |
|----------------|-----------------------------------------------------------------------------------------------------|
| `defaultLimit` | Integer that specifies the default per-page limit that should be used. Defaults to 10. |
| `limits` | Provide an array of integers to use as per-page options for admins to choose from in the List view. |
### Access control
You can specify extremely granular access control (what users can do with documents in a collection) on a collection by
collection basis. To learn more, go to the [Access Control](/docs/access-control/overview) docs.
### Hooks
Hooks are a powerful way to extend collection functionality and execute your own logic, and can be defined on a
collection by collection basis. To learn more, go to the [Hooks](/docs/hooks/overview) documentation.
### Field types
Collections support all field types that Payload has to offer—including simple fields like text and checkboxes all the
way to more complicated layout-building field groups like Blocks. [Click here](/docs/fields/overview) to learn more
about field types.
### List Searchable Fields
In the List view, there is a "search" box that allows you to quickly find a document with a search. By default, it
searches on the ID field. If you have `admin.useAsTitle` defined, the list search will use that field. However, you can
define more than one field to search to make it easier on your admin editors to find the data they need.
For example, let's say you have a Posts collection with `title`, `metaDescription`, and `tags` fields - and you want all
three of those fields to be searchable in the List view. You can simply
add `admin.listSearchableFields: ['title', 'metaDescription', 'tags']` - and the admin UI will automatically search on
those three fields plus the ID field.
<Banner type="warning">
<strong>Note:</strong>
<br />
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields
so your admin queries can remain performant.
</Banner>
### Admin Hooks
In addition to collection hooks themselves, Payload provides for admin UI-specific hooks that you can leverage.
**`beforeDuplicate`**
The `beforeDuplicate` hook is an async function that accepts an object containing the data to duplicate, as well as the
locale of the doc to duplicate. Within this hook, you can modify the data to be duplicated, which is useful in cases
where you have unique fields that need to be incremented or similar, as well as if you want to automatically modify a
document's `title`.
Example:
```ts
import { BeforeDuplicate, CollectionConfig } from 'payload/types'
// Your auto-generated Page type
import { Page } from '../payload-types.ts'
const beforeDuplicate: BeforeDuplicate<Page> = ({ data }) => {
return {
...data,
title: `${data.title} Copy`,
uniqueField: data.uniqueField ? `${data.uniqueField}-copy` : '',
}
}
export const Page: CollectionConfig = {
slug: 'pages',
admin: {
hooks: {
beforeDuplicate,
},
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'uniqueField',
type: 'text',
unique: true,
},
],
}
```
### TypeScript
You can import collection types as follows:
```ts
import { CollectionConfig } from 'payload/types'
// This is the type used for incoming collection configs.
// Only the bare minimum properties are marked as required.
```
```ts
import { SanitizedCollectionConfig } from 'payload/types'
// This is the type used after an incoming collection config is fully sanitized.
// Generally, this is only used internally by Payload.
```

View File

@@ -1,81 +0,0 @@
---
title: Express
label: Express
order: 60
desc: Payload utilizes Express middleware packages, you can customize how they work by passing in configuration options.
keywords: config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload utilizes a few Express-specific middleware packages within its own routers. You can customize how they work by passing in configuration options to the main Payload config's `express` property.
### Custom Middleware
Payload allows you to pass in custom Express middleware to be used on all of the routes it opens. This is useful for adding logging or any other custom functionality to your endpoints.
There are 2 exposed properties. Each property is an array of middleware functions.
- `preMiddleware` - runs before any of the Payload middleware
- `postMiddleware` - runs after all of the Payload middleware
```ts
{
express: {
preMiddleware: [
(req, res, next) => {
// do something
next()
}
],
postMiddleware: [
(req, res, next) => {
// do something
next()
}
]
}
}
// Example logging middleware function
const requestLoggerMiddleware = (req, res, next) => {
req.payload.logger.info(`request: ${req.method} ${req.url}`)
next()
}
```
### JSON
`express.json()` is used to parse JSON body content into JavaScript objects accessible on the Express `req`. Payload allows you to customize all of the `json` method's options. Common examples of customization use-cases are increasing the max allowed JSON body size which defaults to `2MB`.
**Example payload.config.js for how to increase the max JSON size allowed to be sent to Payload endpoints:**
```js
{
express: {
json: {
limit: '4mb',
}
}
}
```
You can find a list of all available options that are able to be passed to `express.json()` [here](https://expressjs.com/en/api.html).
### Compression
Payload uses the `compression` package to optimize transfer size for all of the routes it opens, and you can pass customization options through the Payload config.
To customize compression options, pass an object to the Payload config's `express` property.
**Example payload.config.js:**
```js
{
express: {
compression: {
// settings go here
}
}
}
```
Typically, the default options for this package are suitable. However, for a list of all available customization options, [click here](http://expressjs.com/en/resources/middleware/compression.html).

View File

@@ -1,156 +0,0 @@
---
title: Global Configs
label: Globals
order: 30
desc: Set up your Global config for your needs by defining fields, adding slugs and labels, establishing access control, tying in hooks and more.
keywords: globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Global configs are in many ways similar to [Collections](/docs/configuration/collections). The big difference is that
Collections will potentially contain _many_ documents, while a Global is a "one-off". Globals are perfect for things
like header nav, site-wide banner alerts, app-wide localized strings, and other "global" data that your site or app
might rely on.
As with Collection configs, it's often best practice to write your Globals in separate files and then import them into
the main Payload config.
## Options
| Option | Description |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
| **`label`** | Text for the name in the Admin panel or an object with keys for each language. Auto-generated from slug if not defined. |
| **`description`** | Text or React component to display below the Global header to give editors more information. |
| **`admin`** | Admin-specific configuration. See below for [more detail](/docs/configuration/globals#admin-options). |
| **`hooks`** | Entry points to "tie in" to collection actions at specific points. [More](/docs/hooks/overview#global-hooks) |
| **`access`** | Provide access control functions to define exactly who should be able to do what with this Global. [More](/docs/access-control/overview/#globals) |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#globals-config) |
| **`endpoints`** | Add custom routes to the REST API. [More](/docs/rest-api/overview#custom-endpoints) |
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`dbName`** | Custom table or collection name for this global depending on the database adapter. Auto-generated from slug if not defined.
_\* An asterisk denotes that a property is required._
#### Simple Global example
```ts
import { GlobalConfig } from 'payload/types'
const Nav: GlobalConfig = {
slug: 'nav',
fields: [
{
name: 'items',
type: 'array',
required: true,
maxRows: 8,
fields: [
{
name: 'page',
type: 'relationship',
relationTo: 'pages', // "pages" is the slug of an existing collection
required: true,
},
],
},
],
}
export default Nav
```
#### Global config example
You can find a few [example Global configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/globals)
in the Public Demo source code on GitHub.
### Admin options
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a
Global's config.
| Option | Description |
|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `group` | Text used as a label for grouping collection and global links together in the navigation. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
### Preview
Global `admin` options can accept a `preview` function that will be used to generate a link pointing to the frontend of
your app to preview data.
If the function is specified, a Preview button will automatically appear in the corresponding global's Edit view.
Clicking the Preview button will link to the URL that is generated by the function.
**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.
**Example global with preview function:**
```ts
import { GlobalConfig } from 'payload/types'
export const MyGlobal: GlobalConfig = {
slug: 'my-global',
fields: [
{
name: 'slug',
type: 'text',
required: true,
},
],
admin: {
preview: (doc, { locale }) => {
if (doc?.slug) {
return `https://bigbird.com/preview/${doc.slug}?locale=${locale}`
}
return null
},
},
}
```
### Access control
As with Collections, you can specify extremely granular access control (what users can do with this Global) on a
Global-by-Global basis. However, Globals only have `update` and `read` access control due to their nature of only having
one document. To learn more, go to the [Access Control](/docs/access-control/overview) docs.
### Hooks
Globals also fully support a smaller subset of Hooks. To learn more, go to the [Hooks](/docs/hooks/overview)
documentation.
### Field types
Globals support all field types that Payload has to offer—including simple fields like text and checkboxes all the way
to more complicated layout-building field groups like Blocks. [Click here](/docs/fields/overview) to learn more about
field types.
### TypeScript
You can import global types as follows:
```ts
import { GlobalConfig } from 'payload/types'
// This is the type used for incoming global configs.
// Only the bare minimum properties are marked as required.
```
```ts
import { SanitizedGlobalConfig } from 'payload/types'
// This is the type used after an incoming global config is fully sanitized.
// Generally, this is only used internally by Payload.
```

View File

@@ -1,110 +0,0 @@
---
title: I18n
label: I18n
order: 40
desc: Manage and customize internationalization support in your CMS editor experience
keywords: internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Not only does Payload support managing localized content, it also has internationalization support so that admin users can work in their preferred language. Payload's i18n support is built on top of [i18next](https://www.i18next.com). It comes included by default and can be extended in your config.
While Payload's built-in features come translated, you may want to also translate parts of your project's configuration too. This is possible in places like collections and globals labels and groups, field labels, descriptions and input placeholder text. The admin UI will display all the correct translations you provide based on the user's language.
Here is an example of a simple collection supporting both English and Spanish editors:
```ts
import { CollectionConfig } from 'payload/types'
export const Articles: CollectionConfig = {
slug: 'articles',
labels: {
singular: {
en: 'Article',
es: 'Artículo',
},
plural: {
en: 'Articles',
es: 'Artículos',
},
},
admin: {
group: { en: 'Content', es: 'Contenido' },
},
fields: [
{
name: 'title',
type: 'text',
label: {
en: 'Title',
es: 'Título',
},
admin: {
placeholder: { en: 'Enter title', es: 'Introduce el título' },
},
},
{
name: 'type',
type: 'radio',
options: [
{
value: 'news',
label: { en: 'News', es: 'Noticias' },
}, // etc...
],
},
],
}
```
### Admin UI
The Payload admin panel reads the language settings of a user's browser and display all text in that language, or will fall back to English if the user's language is not yet supported.
After a user logs in, they can change their language selection in the `/account` view.
<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>
### Node Express
Payload's backend uses express middleware to set the language on incoming requests before they are handled. This allows backend validation to return error messages in the user's own language or system generated emails to be sent using the correct translation. You can make HTTP requests with the `accept-language` header and Payload will use that language.
Anywhere in your Payload app that you have access to the `req` object, you can access i18next's extensive internationalization features assigned to `req.i18n`. To access text translations you can use `req.t('namespace:key')`.
Read the i18next [API documentation](https://www.i18next.com/overview/api) to learn more.
### Configuration Options
In your Payload config, you can add translations and customize the settings in `i18n`. Payload will use your custom options and merge it with the default, allowing you to override the settings Payload provides.
**Example Payload config extending i18n:**
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
//...
i18n: {
fallbackLng: 'en', // default
debug: false, // default
resources: {
en: {
custom: {
// namespace can be anything you want
key1: 'Translation with {{variable}}', // translation
},
// override existing translation keys
general: {
dashboard: 'Home',
},
},
},
},
//...
})
```
See the i18next [configuration options](https://www.i18next.com/overview/configuration-options) to learn more.

View File

@@ -1,243 +0,0 @@
---
title: Localization
label: Localization
order: 50
desc: Add and maintain as many locales as you need by adding Localization to your Payload config, set options for default locale, fallbacks, fields and more.
keywords: localization, internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload features deep field-based localization support. Maintaining as many locales as you need is easy. All
localization support is opt-in by default. To do so, follow the two steps below.
### Enabling in the Payload config
Add the `localization` property to your Payload config to enable localization project-wide. You'll need to provide a
list of all locales that you'd like to support as well as set a few other options.
**Example Payload config set up for localization:**
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
collections: [
// collections go here
],
localization: {
locales: ['en', 'es', 'de'], // required
defaultLocale: 'en', // required
fallback: true,
},
})
```
**Example Payload config set up for localization with full locales objects:**
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
collections: [
// collections go here
],
localization: {
locales: [
{
label: 'English',
code: 'en',
},
{
label: 'Arabic',
code: 'ar',
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left)
// when current locale is rtl
rtl: true,
},
],
defaultLocale: 'en', // required
fallback: true,
},
})
```
**Example Payload config set up for localization with full locales objects (
including [internationalization](/docs/configuration/i18n) support):**
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
collections: [
// collections go here
],
localization: {
locales: [
{
label: {
en: 'English', // English label
nb: 'Engelsk', // Norwegian label
},
code: 'en',
},
{
label: {
en: 'Norwegian', // English label
nb: 'Norsk', // Norwegian label
},
code: 'nb',
},
],
defaultLocale: 'en', // required
fallback: true,
},
})
```
**Here is a brief explanation of each of the options available within the `localization` property:**
**`locales`**
Array-based list of all the languages that you would like to support. This can be an array containing strings for each
language code you want your project to store and serve or objects with a `label`, a locale `code`, `rtl` (
right-to-left), and `fallbackLocale` property. The locale codes do not need to be in any specific format. It's up to you
to define how to represent your locales. Common patterns are to use two-letter ISO 639 language codes or four-letter
language and country codes (ISO 31661) such as `en-US`, `en-UK`, `es-MX`, etc.
### Locale Properties:
| Option | Description |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------|
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. |
_\* An asterisk denotes that a property is required._
**`defaultLocale`**
Required string that matches one of the locale codes from the array provided. By default, if no locale is specified,
documents will be returned in this locale.
**`fallback`**
Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a
localized value corresponding to the requested locale, then if this property is enabled, the document will automatically
fall back to the fallback locale value. If this property is not enabled, the value will not be populated.
### Field by field localization
Payload localization works on a **field** level—not a document level. In addition to configuring the base Payload config
to support localization, you need to specify each field that you would like to localize.
**Here is an example of how to enable localization for a field:**
```js
{
name: 'title',
type: 'text',
// highlight-start
localized: true,
// highlight-end
}
```
With the above configuration, the `title` field will now be saved in the database as an object of all locales instead of
a single string.
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s
and `block`s.
<Banner>
<strong>Note:</strong>
<br />
Enabling localization for field types that support nested fields will automatically create
localized "sets" of all fields contained within the field. For example, if you have a page layout
using a blocks field type, you have the choice of either localizing the full layout, by enabling
localization on the top-level blocks field, or only certain fields within the layout.
</Banner>
<Banner type="warning">
<strong>Important:</strong>
<br />
When converting an existing field to or from `localized: true` the data structure in the document
will change for this field and so existing data for this field will be lost. Before changing the
localization setting on fields with existing data, you may need to consider a field migration
strategy.
</Banner>
### Retrieving localized docs
When retrieving documents, you can specify which locale you'd like to receive as well as which fallback locale should be
used.
##### REST API
REST API locale functionality relies on URL query parameters.
**`?locale=`**
Specify your desired locale by providing the `locale` query parameter directly in the endpoint URL.
**`?fallback-locale=`**
Specify fallback locale to be used by providing the `fallback-locale` query parameter. This can be provided as either a
valid locale as provided to your base Payload config, or `'null'`, `'false'`, or `'none'` to disable falling back.
**Example:**
```
fetch('https://localhost:3000/api/pages?locale=es&fallback-locale=none');
```
##### GraphQL API
In the GraphQL API, you can specify `locale` and `fallbackLocale` args to all relevant queries and mutations.
The `locale` arg will only accept valid locales, but locales will be formatted automatically as valid GraphQL enum
values (dashes or special characters will be converted to underscores, spaces will be removed, etc.). If you are curious
to see how locales are auto-formatted, you can use the [GraphQL playground](/docs/graphql/overview#graphql-playground).
The `fallbackLocale` arg will accept valid locales as well as `none` to disable falling back.
**Example:**
```graphql
query {
Posts(locale: de, fallbackLocale: none) {
docs {
title
}
}
}
```
<Banner>
In GraphQL, specifying the locale at the top level of a query will automatically apply it
throughout all nested relationship fields. You can override this behavior by re-specifying locale
arguments in nested related document queries.
</Banner>
##### Local API
You can specify `locale` as well as `fallbackLocale` within the Local API as well as properties on the `options`
argument. The `locale` property will accept any valid locale, and the `fallbackLocale` property will accept any valid
locale as well as `'null'`, `'false'`, `false`, and `'none'`.
**Example:**
```js
const posts = await payload.find({
collection: 'posts',
locale: 'es',
fallbackLocale: false,
})
```
<Banner type="alert">
<strong>Tip:</strong>
<br />
The REST and Local APIs can return all localization data in one request by passing 'all' or '*' as
the <strong>locale</strong> parameter. The response will be structured so that field values come
back as the full objects keyed for each locale instead of the single, translated value.
</Banner>

View File

@@ -1,197 +0,0 @@
---
title: The Payload Config
label: Overview
order: 10
desc: The Payload config is central to everything that Payload does, from adding custom React components, to modifying collections, controlling localization and much more.
keywords: overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload is a _config-based_, code-first CMS and application framework. The Payload config is central to everything that Payload does. It scaffolds the data that Payload stores as well as maintains custom React components, hook logic, custom validations, and much more.
**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 />
This file is 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 bundler*, 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) |
| `express` | Express-specific middleware options such as compression and JSON parsing. [More](/docs/configuration/express) |
| `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) |
_\* An asterisk denotes that a property is required._
#### Simple example
```ts
import { buildConfig } from 'payload/config'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { postgresAdapter } from '@payloadcms/db-postgres' // beta
import { viteBundler } from '@payloadcms/bundler-vite'
import { webpackBundler } from '@payloadcms/bundler-webpack'
import { lexicalEditor } from '@payloadcms/richtext-lexical' // beta
import { slateEditor } from '@payloadcms/richtext-slate'
export default buildConfig({
admin: {
bundler: webpackBundler(), // or viteBundler()
},
db: mongooseAdapter({}), // or postgresAdapter({})
editor: lexicalEditor({}), // or slateEditor({})
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
required: true,
},
],
},
],
globals: [
{
slug: 'header',
fields: [
{
name: 'nav',
type: 'array',
fields: [
{
name: 'page',
type: 'relationship',
relationTo: 'pages',
},
],
},
],
},
],
})
```
#### Full example config
You can see a full [example config](https://github.com/payloadcms/public-demo/blob/master/src/payload/payload.config.ts) in the Public Demo source code on GitHub.
### 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.
**Add this line to the top of your server:**
```
require('dotenv').config()
// ...
// the rest of your `server.js` 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
```
<Banner type="warning">
<strong>Important:</strong>
<br />
If you use an environment variable to configure any properties that are required for the Admin
panel to function (ex. serverURL or any routes), you need to make sure that your Admin panel code
can access it. [Click here](/docs/admin/webpack#admin-environment-vars) for more info.
</Banner>
### 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.
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").
In production mode, Payload will first attempt to find the config file in the output directory specified in `compilerOptions.outDir` of your `tsconfig.json`, then fallback to the source directory (`compilerOptions.rootDir`), and finally will check the 'dist' directory.
Please ensure your `tsconfig.json` is properly configured if you want Payload to accurately auto-detect your configuration file location. If `tsconfig.json` does not exist or doesn't specify `rootDir` or `outDir`, Payload will default to the current working directory.
#### Overriding the Config Location
In addition to the above automated detection, you can specify your own location for the Payload config file. This is done by using the environment variable `PAYLOAD_CONFIG_PATH`. The path you provide via this environment variable can either be absolute or relative to your current working directory. This can be useful in situations where your Payload config is not in a standard location, or you wish to switch between multiple configurations.
**Example in package.json:**
```json
{
"scripts": {
"dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js"
}
}
```
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
You can import config types as follows:
```ts
import { Config } from 'payload/config'
// 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'
// This is the type used after an incoming Payload config is fully sanitized.
// Generally, this is only used internally by Payload.
```
### 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.
For more information about what we track, take a look at our [privacy policy](/privacy).

View File

@@ -1,125 +0,0 @@
---
title: Migrations
label: Migrations
order: 20
keywords: database, migrations, ddl, sql, mongodb, postgres, documentation, Content Management System, cms, headless, typescript, node, react, express
desc: Payload features first-party database migrations all done in TypeScript.
---
Payload exposes a full suite of migration controls available for your use. Migration commands are accessible via
the `npm run payload` command in your project directory.
Ensure you have an npm script called "payload" in your `package.json` file.
```json
{
"scripts": {
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
}
}
```
<Banner>
Note that you need to run Payload migrations through the package manager that you are using, because Payload should not be globally installed on your system.
</Banner>
### Migration file contents
Payload stores all created migrations in a folder that you can specify. By default, migrations are stored
in `./src/migrations`.
A migration file has two exports - an `up` function, which is called when a migration is executed, and a `down` function
that will be called if for some reason the migration fails to complete successfully. The `up` function should contain
all changes that you attempt to make within the migration, and the `down` should ideally revert any changes you make.
For an added level of safety, migrations should leverage Payload [transactions](/docs/database/transactions). Migration
functions should make use of the `req` by adding it to the arguments of your payload local API calls such
as `payload.create` and database adapter methods like `payload.db.create`.
Here is an example migration file:
```ts
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
export async function up ({ payload, req }: MigrateUpArgs): Promise<void> {
// Perform changes to your database here.
// You have access to `payload` as an argument, and
// everything is done in TypeScript.
};
export async function down ({ payload, req }: MigrateDownArgs): Promise<void> {
// Do whatever you need to revert changes if the `up` function fails
};
```
### Migrations Directory
Each DB adapter has an optional property `migrationDir` where you can override where you want your migrations to be
stored/read. If this is not specified, Payload will check the default and possibly make a best effort to find your
migrations directory by searching in common locations ie. `./src/migrations`, `./dist/migrations`, `./migrations`, etc.
All database adapters should implement similar migration patterns, but there will be small differences based on the
adapter and its specific needs. Below is a list of all migration commands that should be supported by your database
adapter.
## Commands
### Migrate
The `migrate` command will run any migrations that have not yet been run.
```text
npm run payload migrate
```
### Create
Create a new migration file in the migrations directory. You can optionally name the migration that will be created. By
default, migrations will be named using a timestamp.
```text
npm run payload migrate:create optional-name-here
```
### Status
The `migrate:status` command will check the status of migrations and output a table of which migrations have been run,
and which migrations have not yet run.
`payload migrate:status`
```text
npm run payload migrate:status
```
### Down
Roll back the last batch of migrations.
```text
npm run payload migrate:down
```
### Refresh
Roll back all migrations that have been run, and run them again.
```text
npm run payload migrate:refresh
```
### Reset
Roll back all migrations.
```text
npm run payload migrate:reset
```
### Fresh
Drops all entities from the database and re-runs all migrations from scratch.
```text
npm run payload migrate:fresh
```

View File

@@ -1,103 +0,0 @@
---
title: MongoDB
label: MongoDB
order: 40
desc: Payload has supported MongoDB natively since we started. The flexible nature of MongoDB lends itself well to Payload's powerful fields.
keywords: MongoDB, documentation, typescript, Content Management System, cms, headless, javascript, node, react, express
---
To use Payload with MongoDB, install the package `@payloadcms/db-mongodb`. It will come with everything you need to
store your Payload data in MongoDB.
Then from there, pass it to your Payload config as follows:
```ts
import { mongooseAdapter } from '@payloadcms/db-mongodb'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Configure the Mongoose adapter here
db: mongooseAdapter({
// Mongoose-specific arguments go here.
// URL is required.
url: process.env.DATABASE_URI,
}),
})
```
### Options
| Option | Description |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `schemaOptions` | Customize schema options for all Mongoose schemas created internally. |
| `jsonParse` | Set to false to disable the automatic JSON stringify/parse of data queried by MongoDB. For example, if you have data not tracked by Payload such as `Date` fields and similar, you can use this option to ensure that existing `Date` properties remain as `Date` and not strings. |
| `collections` | Options on a collection-by-collection basis. [More](#collections-options) |
| `globals` | Options for the Globals collection created by Payload. [More](#globals-options) |
| `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. |
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
### Access to Mongoose models
After Payload is initialized, this adapter exposes all of your Mongoose models and they are available for you to work
with directly.
You can access Mongoose models as follows:
- Collection models - `payload.db.collections[myCollectionSlug]`
- Globals model - `payload.db.globals`
- Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]`
### Collections Options
You can configure the way the MongoDB adapter works on a collection-by-collection basis, including customizing Mongoose `schemaOptions` for each collection schema created.
Example:
```ts
const db = mongooseAdapter({
url: 'your-url-here',
collections: {
users: {
//
schemaOptions: {
strict: false,
}
}
}
})
```
### Global Options
Payload automatically creates a single `globals` collection that correspond with any Payload globals that you define. When you initialize the `mongooseAdapter`, you can specify settings here for your globals in a similar manner to how you can for collections above. Right now, the only property available is `schemaOptions` but more may be added in the future.
### Preserving externally managed data
You can use Payload in conjunction with an existing MongoDB database, where you might have some fields "tracked" in Payload via corresponding field configs, and other fields completely unknown to Payload.
If you have external field data in existing MongoDB collections which you'd like to use in combination with Payload, and you don't want to lose those external fields, you can configure Payload to "preserve" that data while it makes updates to your existing documents.
To do this, the first step is to configure Mongoose's `strict` property, which tells Mongoose to write all data that it receives (and not disregard any data that it does not know about).
The second step is to disable Payload's automatic JSON parsing of documents it receives from MongoDB.
Here's an example for how to configure your Mongoose adapter to preserve external collection fields that are not tracked by Payload:
```ts
mongooseAdapter({
url: process.env.DATABASE_URI,
// Disable the JSON parsing that Payload performs
jsonParse: false,
// Disable strict mode for Mongoose
schemaOptions: {
strict: false,
},
})
```

View File

@@ -1,174 +0,0 @@
---
title: Database
label: Overview
order: 10
keywords: database, mongodb, postgres, documentation, Content Management System, cms, headless, typescript, node, react, express
desc: With Payload, you bring your own database and own your data. You have full control.
---
Payload interacts with your database via the database adapter that you choose. Right now, Payload officially supports two database adapters:
1. [MongoDB](/docs/database/mongodb) w/ [Mongoose](https://mongoosejs.com/)
1. [Postgres](/docs/database/postgres) w/ [Drizzle](https://drizzle.team/)
We will be adding support for SQLite and MySQL in the near future using Drizzle ORM.
To use a specific database adapter, you need to install it and configure it according to its own specifications. Visit the documentation for your applicable database adapter to learn more.
## Selecting a database
There are several factors to consider when choosing which database technology and hosting option is right for your project and workload. Payload can theoretically support any database, but it's up to you to decide which database to use.
#### When to use MongoDB
If your project has a lot of dynamic fields, and you are comfortable with allowing Payload to enforce data integrity across your documents, MongoDB is a great choice. With it, your Payload documents are stored as _one_ document in your database—no matter if you have localization enabled, how many block or array fields you have, etc. This means that the shape of your data in your database will very closely reflect your field schema, and there is minimal complexity involved in storing or retrieving your data.
You should prefer MongoDB if:
- You prefer simplicity within your database
- You don't want to deal with keeping production / staging databases in sync via [DDL changes](https://en.wikipedia.org/wiki/Data_definition_language)
- Most (or everything) in your project is localized
- You leverage a lot of array fields, block fields, or `hasMany` select fields and similar
#### When to use a relational DB
Many projects might call for more rigid database architecture where the shape of your data is strongly enforced at the database level. For example, if you know the shape of your data and it's relatively "flat", and you don't anticipate it to change often, your workload might suit relational databases like Postgres very well.
You should prefer a relational DB like Postgres if:
- You are comfortable with migration workflows
- You require enforced data consistency at the database level
- You have a lot of relationships between collections and require relationships to be enforced
#### Differences in Payload features
It's important to note that almost everything Payload does is available in all of our officially supported database adapters, including localization, arrays, blocks, etc.
The only thing that is not supported in Postgres yet is the [Point field](/docs/fields/point), but that should be added soon.
It's up to you to choose which database you would like to use.
## Configuration
To configure the database for your Payload application, an adapter can be assigned to `config.db`. This property is required within your Payload config.
Here's an example:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Here is where you pass your database adapter
// and the adapter will require options specific to itself
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI,
}
}),
})
```
## Collection Operations
To configure Collection database operations in your Payload application, your Collection config has methods that can override default database operations for that Collection.
The override methods receive arguments useful for augmenting operations such as Field data, the collection slug, and the req.
Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'
export const Collection: CollectionConfig => {
return {
slug: 'collection-db-operations',
db: {
// Create a document in a custom db
create: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/create`, {
method: "POST",
body: JSON.stringify(data),
headers: {
'x-app-user': `payload_${req.payload.user}`,
'Content-Type': 'application/json'
}
}).then(response => response.json())
return doc
},
// Delete a document in a custom db
deleteOne: async ({ collection, data, req }) => {
const docs = await fetch(`https://example.com/api/${collection}/delete/${data.id}`, {
method: 'DELETE',
headers: {
'x-app-user': `payload_${req.payload.user}`
}
}).then(response => response.json())
return docs
},
// Delete many documents in a custom db
deleteMany: async ({ collection, data, req }) => {
const docs = await fetch(`https://example.com/api/${collection}/delete`, {
method: 'DELETE'
headers: {
'x-app-user': `payload_${req.payload.user}`
}
body: JSON.stringify(data),
}).then(response => response.json())
return docs
},
// Find documents in a custom db
find: async ({ collection, data, req, where, limit }) => {
const docs = await fetch(`https://example.com/api/${collection}/find`, {
headers: {
'x-app-user': `payload_${req.payload.user}`
}
body: JSON.stringify({data, where, limit}),
}).then(response => response.json())
return { docs }
},
// Find one document in a custom db
findOne: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/find/${data.id}`, {
headers: {
'x-app-user': `payload_${req.payload.user}`
}
}).then(response => response.json())
return doc
},
// Update one document in an custom db
updateOne: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/update/${data.id}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: {
'x-app-user': `payload_${req.payload.user}`,
'Content-Type': 'application/json'
}
}).then(response => response.json())
return { ...doc, updated: true }
},
},
fields: [
{
name: 'name',
type: 'text',
},
],
}
}
```

View File

@@ -1,94 +0,0 @@
---
title: Postgres
label: Postgres
order: 50
desc: Payload supports Postgres through an officially supported Drizzle database adapter.
keywords: Postgres, documentation, typescript, Content Management System, cms, headless, javascript, node, react, express
---
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:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Configure the Postgres adapter here
db: postgresAdapter({
// Postgres-specific arguments go here.
// `pool` is required.
pool: {
connectionString: process.env.DATABASE_URI,
}
}),
})
```
### Options
| Option | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
### Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
You can access Drizzle as follows:
```text
payload.db.drizzle
```
### Tables, relations, and enums
In addition to exposing Drizzle directly, all of the tables, Drizzle relations, and enum configs are exposed for you via the `payload.db` property as well.
- Tables - `payload.db.tables`
- Enums - `payload.db.enums`
- Relations - `payload.db.relations`
### Prototyping in development mode
Drizzle exposes two ways to work locally in development mode.
The first is [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push), which automatically pushes changes you make to your Payload config (and therefore, Drizzle schema) to your database so you don't have to manually migrate every time you change your Payload config. This only works in development mode, and should not be mixed with manually running [`migrate`](/docs/database/migrations) commands.
You will be warned if any changes that you make will entail data loss while in development mode. Push is enabled by default, but you can opt out if you'd like.
Alternatively, you can disable `push` and rely solely on migrations to keep your local database in sync with your Payload config.
### Migration workflows
Migrations are extremely powerful thanks to the seamless way that Payload and Drizzle work together. Let's take the following scenario:
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>

View File

@@ -1,104 +0,0 @@
---
title: Transactions
label: Transactions
order: 30
keywords: database, transactions, sql, mongodb, postgres, documentation, Content Management System, cms, headless, typescript, node, react, express
desc: Database transactions are fully supported within Payload.
---
Database transactions allow your application to make a series of database changes in an all-or-nothing commit. Consider an HTTP request that creates a new **Order** and has an `afterChange` hook to update the stock count of related **Items**. If an error occurs when updating an **Item** and an HTTP error is returned to the user, you would not want the new **Order** to be persisted or any other items to be changed either. This kind of interaction with the database is handled seamlessly with transactions.
By default, Payload will use transactions for all operations, as long as it is supported by the configured database. Database changes are contained within all Payload operations and any errors thrown will result in all changes being rolled back without being committed. When transactions are not supported by the database, Payload will continue to operate as expected without them.
<Banner type="info">
<strong>Note:</strong>
<br />
MongoDB requires a connection to a replicaset in order to make use of transactions.
</Banner>
The initial request made to Payload will begin a new transaction and attach it to the `req.transactionID`. If you have a `hook` that interacts with the database, you can opt-in to using the same transaction by passing the `req` in the arguments. For example:
```ts
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
// because req.transactionID is assigned from Payload and passed through,
// my-slug will only persist if the entire request is successful
await req.payload.create({
req,
collection: 'my-slug',
data: {
some: 'data',
},
})
}
```
### Async Hooks with Transactions
Since Payload hooks can be async and be written to not await the result, it is possible to have an incorrect success response returned on a request that is rolled back. If you have a hook where you do not `await` the result, then you should **not** pass the `req.transactionID`.
```ts
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
// WARNING: an async call made with the same req, but NOT awaited,
// may fail resulting in an OK response being returned with response data that is not committed
const dangerouslyIgnoreAsync = req.payload.create({
req,
collection: 'my-slug',
data: {
some: 'other data',
},
})
// Should this call fail, it will not rollback other changes
// because the req (and its transactionID) is not passed through
const safelyIgnoredAsync = req.payload.create({
collection: 'my-slug',
data: {
some: 'other data',
},
})
}
```
### Direct Transaction Access
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database in something like a background job, outside the normal request-response flow.
The following functions can be used for managing transactions:
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls. Note that if your database does not support transactions, this will return `null`.\
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.\
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
You can then use the transaction ID with Payload's local API by passing it inside the `PayloadRequest` object.
Here is an example for a "background job" function, which utilizes the direct transaction API to make sure it either succeeds completely or gets rolled back in case of an error.
```ts
async function allOrNothingJob() {
const req = {} as PayloadRequest;
req.transactionID = await payload.db.beginTransaction();
try {
await payload.create({
req, // use our manual transaction
collection: 'my-slug',
data: {
some: 'data'
}
});
await payload.create({
req, // use our manual transaction
collection: 'something-else',
data: {
some: 'data'
}
});
console.log('Everything done.');
if (req.transactionID) await payload.db.commitTransaction(req.transactionID);
} catch (e) {
console.error('Oh no, something went wrong!');
if (req.transactionID) await payload.db.rollbackTransaction(req.transactionID);
}
}
```

View File

@@ -1,167 +0,0 @@
---
title: Email Functionality
label: Overview
order: 10
desc: Payload uses NodeMailer to allow you to send emails smoothly from your app. Set up email functions such as password resets, order confirmations and more.
keywords: email, overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
### Introduction
Payload comes ready to send your application's email. Whether you simply need built-in password reset
email to work or you want customers to get an order confirmation email, you're almost there. Payload makes use of
[NodeMailer](https://nodemailer.com) for email and won't get in your way for those already familiar.
For email to send from your Payload server, some configuration is required. The settings you provide will be set
in the `email` property object of your payload init call. Payload will make use of the transport that you have configured for it for things like reset password or verifying new user accounts and email send methods are available to you as well on your payload instance.
### Configuration
**Three ways to set it up**
1. **Default**: When email is not needed, a mock email handler will be created and used when nothing is provided. This is ideal for development environments and can be changed later when ready to [go to production](/docs/production/deployment).
1. **Recommended**: Set the `transportOptions` and Payload will do the set up for you.
1. **Advanced**: The `transport` object can be assigned a nodemailer transport object set up in your server scripts and given for Payload to use.
The following options are configurable in the `email` property object as part of the options object when calling payload.init().
| Option | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`fromName`** \* | The name part of the From field that will be seen on the delivered email |
| **`fromAddress`** \* | The email address part of the From field that will be used when delivering email |
| **`transport`** | The NodeMailer transport object for when you want to do it yourself, not needed when transportOptions is set |
| **`transportOptions`** | An object that configures the transporter that Payload will create. For all the available options see the [NodeMailer documentation](https://nodemailer.com) or see the examples below |
| **`logMockCredentials`** | If set to true and no transport/transportOptions, ethereal credentials will be logged to console on startup |
_\* An asterisk denotes that a property is required._
### Use SMTP
Simple Mail Transfer Protocol (SMTP) options can be passed in using the `transportOptions` object on the `email` options. See the [NodeMailer SMTP documentation](https://nodemailer.com/smtp/) for more information, including details on when `secure` should and should not be set to `true`.
**Example email options using SMTP:**
```ts
payload.init({
email: {
transportOptions: {
host: process.env.SMTP_HOST,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
port: Number(process.env.SMTP_HOST),
secure: Number(process.env.SMTP_PORT) === 465, // true for port 465, false (the default) for 587 and others
requireTLS: true,
},
fromName: 'hello',
fromAddress: 'hello@example.com',
},
// ...
})
```
<Banner type="warning">
It is best practice to avoid saving credentials or API keys directly in your code, use
[environment variables](/docs/configuration/overview#using-environment-variables-in-your-config).
</Banner>
### Use an email service
Many third party mail providers are available and offer benefits beyond basic SMTP. As an example, your payload init could look like this if you wanted to use SendGrid.com, though the same approach would work for any other [NodeMailer transports](https://nodemailer.com/transports/) shown here or provided by another third party.
```ts
import payload from 'payload'
import nodemailerSendgrid from 'nodemailer-sendgrid'
const sendGridAPIKey = process.env.SENDGRID_API_KEY
payload.init({
...(sendGridAPIKey
? {
email: {
transportOptions: nodemailerSendgrid({
apiKey: sendGridAPIKey,
}),
fromName: 'Admin',
fromAddress: 'admin@example.com',
},
}
: {}),
})
```
### Use a custom NodeMailer transport
To take full control of the mail transport you may wish to use `nodemailer.createTransport()` on your server and provide it to Payload init.
```ts
import payload from 'payload'
import nodemailer from 'nodemailer'
const payload = require('payload')
const nodemailer = require('nodemailer')
const transport = await nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
payload.init({
email: {
fromName: 'Admin',
fromAddress: 'admin@example.com',
transport,
},
// ...
})
```
### Sending Mail
With a working transport you can call it anywhere you have access to payload by calling `payload.sendEmail(message)`. The `message` will contain the `to`, `subject` and `email` or `text` for the email being sent. To see all available message configuration options see [NodeMailer](https://nodemailer.com/message).
### Mock transport
By default, Payload uses a mock implementation that only sends mail to the [ethereal](https://ethereal.email) capture service that will never reach a user's inbox. While in development you may wish to make use of the captured messages which is why the payload output during server output helpfully logs this out on the server console.
To see ethereal credentials, add `logMockCredentials: true` to the email options. This will cause them to be logged to console on startup.
```ts
payload.init({
email: {
fromName: 'Admin',
fromAddress: 'admin@example.com',
logMockCredentials: true, // Optional
},
// ...
})
```
**Console output when starting payload with a mock email instance and logMockCredentials: true**
```
[06:37:21] INFO (payload): Starting Payload...
[06:37:22] INFO (payload): Payload Demo Initialized
[06:37:22] INFO (payload): listening on 3000...
[06:37:22] INFO (payload): Connected to MongoDB server successfully!
[06:37:23] INFO (payload): E-mail configured with mock configuration
[06:37:23] INFO (payload): Log into mock email provider at https://ethereal.email
[06:37:23] INFO (payload): Mock email account username: hhav5jw7doo4euev@ethereal.email
[06:37:23] INFO (payload): Mock email account password: VNdGcvDZeyEhtuPBqf
```
The mock email handler is used when payload is started with neither `transport` or `transportOptions` to know how to deliver email.
<Banner type="warning">
The randomly generated email account username and password will be different each time the Payload
server starts.
</Banner>
### Using multiple mail providers
Payload supports the use of a single transporter of email, but there is nothing stopping you from having more. Consider a use case where sending bulk email is handled differently than transactional email and could be done using a [hook](/docs/hooks/overview).

View File

@@ -1,39 +0,0 @@
---
title: Examples
label: Overview
order: 10
desc:
keywords: example, examples, starter, boilerplate, template, templates
---
Payload provides a vast array of examples to help you get started with your project no matter what you are working on. These examples are designed to be easy to get up and running, and to be easy to understand. They showcase nothing more than the specific features being demonstrated, so you can easily decipher what is going on.
Examples are changing every day, so be sure to check back often to see what new examples have been added. If you have a specific example you would like to see, 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.
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)
- [Form Builder](https://github.com/payloadcms/payload/tree/main/examples/form-builder)
- [Hierarchy](https://github.com/payloadcms/payload/tree/main/examples/hierarchy)
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
- [Nested Docs](https://github.com/payloadcms/payload/tree/main/examples/nested-docs)
- [Redirects](https://github.com/payloadcms/payload/tree/main/examples/redirects)
- [Tests](https://github.com/payloadcms/payload/tree/main/examples/testing)
- [Virtual Fields](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields)
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
Where necessary, some examples include a front-end. Examples that require a front-end share this folder structure:
```plaintext
example/
├── payload/
├── next-app/
├── next-pages/
├── react-router/
├── vue/
├── 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.

View File

@@ -1,114 +0,0 @@
---
title: Array Field
label: Array
order: 20
desc: Array fields are intended for sets of repeating fields, that you define. Learn how to use array fields, see examples and options.
keywords: array, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Array field type is used when you need to have a set of "repeating" fields. It stores an array
of objects containing the fields that you define. Its uses can be simple or highly complex.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/array.png"
srcDark="https://payloadcms.com/images/docs/fields/array-dark.png"
alt="Array field with two Rows in Payload admin panel"
caption="Admin panel screenshot of an Array field with two Rows"
/>
**Example uses:**
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
- Navigational structures where editors can specify nav items containing
pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)),
label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
### Config
| Option | Description |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following
properties:
| Option | Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable array order sorting by setting this value to `false` |
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
import { RowLabelArgs } from 'payload/dist/admin/components/forms/RowLabel/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'slider', // required
type: 'array', // required
label: 'Image Slider',
minRows: 2,
maxRows: 10,
interfaceName: 'CardSlider', // optional
labels: {
singular: 'Slide',
plural: 'Slides',
},
fields: [
// required
{
name: 'title',
type: 'text',
},
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: true,
},
{
name: 'caption',
type: 'text',
},
],
admin: {
components: {
RowLabel: ({ data, index }: RowLabelArgs) => {
return data?.title || `Slide ${String(index).padStart(2, '0')}`
},
},
},
},
],
}
```

View File

@@ -1,152 +0,0 @@
---
title: Blocks Field
label: Blocks
order: 30
desc: The Blocks field type is a great layout build and can be used to construct any flexible content model. Learn how to use Block fields, see examples and options.
keywords: blocks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Blocks field type is <strong>incredibly powerful</strong> and can be used as a{' '}
<em>layout builder</em> as well as to define any other flexible content model you can think of. It
stores an array of objects, where each object must match the shape of one of your provided block
configs.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
srcDark="https://payloadcms.com/images/docs/fields/blocks-dark.png"
alt="Admin panel screenshot of add Blocks drawer view"
caption="Admin panel screenshot of add Blocks drawer view"
/>
**Example uses:**
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include
configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
### Field config
| Option | Description |
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-level hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-level access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following
properties:
| Option | Description |
|---------------------|---------------------------------|
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable block order sorting by setting this value to `false` |
### Block configs
Blocks are defined as separate configs of their own.
<Banner type="success">
<strong>Tip:</strong>
<br />
Best practice is to define each block config in its own file, and then import them into your
Blocks field as necessary. This way each block config can be easily shared between fields. For
instance, using the "layout builder" example, you might want to feature a few of the same blocks
in a Post collection as well as a Page collection. Abstracting into their own files trivializes
their reusability.
</Banner>
| Option | Description |
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
| **`fields`** \* | Array of fields to be stored in this block. |
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
| **`imageURL`** | Provide a custom image thumbnail to help editors identify this block in the Admin UI. |
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
| **`dbName`** | Custom table name for this block type when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined.
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
#### Auto-generated data per block
In addition to the field data that you define on each block, Payload will store two additional properties on each block:
**`blockType`**
The `blockType` is saved as the slug of the block that has been selected.
**`blockName`**
The Admin panel provides each block with a `blockName` field which optionally allows editors to label their blocks for
better editability and readability.
### Example
`collections/ExampleCollection.js`
```ts
import { Block, CollectionConfig } from 'payload/types'
const QuoteBlock: Block = {
slug: 'Quote', // required
imageURL: 'https://google.com/path/to/image.jpg',
imageAltText: 'A nice thumbnail image to show what this block looks like',
interfaceName: 'QuoteBlock', // optional
fields: [
// required
{
name: 'quoteHeader',
type: 'text',
required: true,
},
{
name: 'quoteText',
type: 'text',
},
],
}
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'layout', // required
type: 'blocks', // required
minRows: 1,
maxRows: 20,
blocks: [
// required
QuoteBlock,
],
},
],
}
```
### TypeScript
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do
so, you can import and use Payload's `Block` type:
```ts
import type { Block } from 'payload/types'
```

View File

@@ -1,56 +0,0 @@
---
title: Checkbox Field
label: Checkbox
order: 40
desc: Checkbox field types allow the developer to save a boolean value in the database. Learn how to use Checkbox fields, see examples and options.
keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>The Checkbox field type saves a boolean in the database.</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/checkbox.png"
srcDark="https://payloadcms.com/images/docs/fields/checkbox-dark.png"
alt="Checkbox field with text field in Payload admin panel"
caption="Admin panel screenshot of Checkbox field with Text field below"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'enableCoolStuff', // required
type: 'checkbox', // required
label: 'Click me to see fanciness',
defaultValue: false,
},
],
}
```

View File

@@ -1,76 +0,0 @@
---
title: Code Field
label: Code
order: 50
desc: The Code field type will store any string in the Database. Learn how to use Code fields, see examples and options.
keywords: code, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Code field type saves a string in the database, but provides the Admin panel with a code
editor styled interface.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/code.png"
srcDark="https://payloadcms.com/images/docs/fields/code-dark.png"
alt="Shows a Code field in the Payload admin panel"
caption="Admin panel screenshot of a Code field"
/>
This field uses the `monaco-react` editor syntax highlighting.
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database#overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`language`** | This property can be set to any language listed [here](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages). |
| **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IDiffEditorConstructionOptions.html). |
### Example
`collections/ExampleCollection.ts
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'trackingCode', // required
type: 'code', // required
required: true,
admin: {
language: 'javascript',
},
},
],
}
```

View File

@@ -1,69 +0,0 @@
---
title: Collapsible Field
label: Collapsible
order: 60
desc: With the Collapsible field, you can place fields within a collapsible layout component that can be collapsed / expanded.
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Collapsible field is presentational-only and only affects the Admin panel. By using it, you
can place fields within a nice layout component that can be collapsed / expanded.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/collapsible.png"
srcDark="https://payloadcms.com/images/docs/fields/collapsible-dark.png"
alt="Shows a Collapsible field in the Payload admin panel"
caption="Admin panel screenshot of a Collapsible field"
/>
### Config
| Option | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
| **`fields`** \* | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
label: ({ data }) => data?.title || 'Untitled',
type: 'collapsible', // required
fields: [
// required
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'someTextField',
type: 'text',
required: true,
},
],
},
],
}
```

View File

@@ -1,116 +0,0 @@
---
title: Date Field
label: Date
order: 70
desc: The Date field type stores a Date in the database. Learn how to use and customize the Date field, see examples and options.
keywords: date, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Date field type saves a Date in the database and provides the Admin panel with a customizable
time picker interface.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/date.png"
srcDark="https://payloadcms.com/images/docs/fields/date-dark.png"
alt="Shows a Date field in the Payload admin panel"
caption="Admin panel screenshot of a Date field"
/>
This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepicker) for the Admin panel component.
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can customize the following fields that will adjust how the component displays in the admin panel via the `date` property.
| Property | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------- |
| **`placeholder`** | Placeholder text for the field. |
| **`date`** | Pass options to customize date field appearance. |
| **`date.displayFormat`** | Format date to be shown in field **cell**. |
| **`date.pickerAppearance`** \* | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
| **`date.monthsToShow`** \* | Number of months to display max is 2. Defaults to 1. |
| **`date.minDate`** \* | Min date value to allow. |
| **`date.maxDate`** \* | Max date value to allow. |
| **`date.minTime`** \* | Min time value to allow. |
| **`date.maxTime`** \* | Max date value to allow. |
| **`date.overrides`** \* | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
| **`date.timeIntervals`** \* | Time intervals to display. Defaults to 30 minutes. |
| **`date.timeFormat`** \* | Determines time format. Defaults to `'h:mm aa'`. |
_\* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md). ._
#### Display Format and Picker Appearance
These properties only affect how the date is displayed in the UI. The full date is always stored in the format `YYYY-MM-DDTHH:mm:ss.SSSZ` (e.g. `1999-01-01T8:00:00.000+05:00`).
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v2.29.3/docs/format].
`pickerAppearance` sets the appearance of the **react datepicker**, the options available are `dayAndTime`, `dayOnly`, `timeOnly`, and `monthOnly`. By default, the datepicker will display `dayOnly`.
When only `pickerAppearance` is set, an equivalent format will be rendered in the date field cell. To overwrite this format, set `displayFormat`.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'dateOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayOnly',
displayFormat: 'd MMM yyy',
},
},
},
{
name: 'timeOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'timeOnly',
displayFormat: 'h:mm:ss a',
},
},
},
{
name: 'monthOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'monthOnly',
displayFormat: 'MMMM yyyy',
},
},
},
],
}
```

View File

@@ -1,69 +0,0 @@
---
title: Email Field
label: Email
order: 80
desc: The Email field enforces that the value provided is a valid email address. Learn how to use Email fields, see examples and options.
keywords: email, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>The Email field enforces that the value provided is a valid email address.</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/email.png"
srcDark="https://payloadcms.com/images/docs/fields/email-dark.png"
alt="Shows an Email field in the Payload admin panel"
caption="Admin panel screenshot of an Email field"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
**`placeholder`**
Set this property to define a placeholder string for the field.
**`autoComplete`**
Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'contact', // required
type: 'email', // required
label: 'Contact Email Address',
required: true,
},
],
}
```

View File

@@ -1,83 +0,0 @@
---
title: Group Field
label: Group
order: 90
desc: The Group field allows other fields to be nested under a common property. Learn how to use Group fields, see examples and options.
keywords: group, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Group field allows fields to be nested under a common property name. It also groups fields
together visually in the Admin panel.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/group.png"
srcDark="https://payloadcms.com/images/docs/fields/group-dark.png"
alt="Shows a Group field in the Payload admin panel"
caption="Admin panel screenshot of a Group field"
/>
### Config
| Option | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`fields`** \* | Array of field types to nest within this Group. |
| **`label`** | Used as a heading in the Admin panel and to name the generated GraphQL type. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Group allows for the following admin property:
**`hideGutter`**
Set this property to `true` to hide this field's gutter within the admin panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageMeta', // required
type: 'group', // required
interfaceName: 'Meta', // optional
fields: [
// required
{
name: 'title',
type: 'text',
required: true,
minLength: 20,
maxLength: 100,
},
{
name: 'description',
type: 'textarea',
required: true,
minLength: 40,
maxLength: 160,
},
],
},
],
}
```

View File

@@ -1,136 +0,0 @@
---
title: JSON Field
label: JSON
order: 50
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The JSON field type saves actual JSON in the database, which differs from the Code field that
saves the value as a string in the database.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/json.png"
srcDark="https://payloadcms.com/images/docs/fields/json-dark.png"
alt="Shows a JSON field in the Payload admin panel"
caption="Admin panel screenshot of a JSON field"
/>
This field uses the `monaco-react` editor syntax highlighting.
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/typedoc/variables/editor.EditorOptions.html). |
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
required: true,
},
],
}
```
### JSON Schema Validation
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
#### Local JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'a://b/foo.json', // required
fileMatch: ['a://b/foo.json'], // required
schema: {
type: 'object',
properties: {
foo: {
enum: ['bar', 'foobar'],
}
},
},
},
},
],
}
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```
#### Remote JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'https://example.com/customer.schema.json', // required
fileMatch: ['https://example.com/customer.schema.json'], // required
},
},
],
}
// If 'https://example.com/customer.schema.json' has a JSON schema
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```

View File

@@ -1,83 +0,0 @@
---
title: Number Field
label: Number
order: 100
desc: Number fields store and validate numeric data. Learn how to use and format Number fields, see examples and Number field options.
keywords: number, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Number field stores and validates numeric entry and supports additional numerical validation
and formatting features.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/number.png"
srcDark="https://payloadcms.com/images/docs/fields/number-dark.png"
alt="Shows a Number field in the Payload admin panel"
caption="Admin panel screenshot of a Number field"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
**`step`**
Set a value for the number field to increment / decrement using browser controls.
**`placeholder`**
Set this property to define a placeholder string for the field.
**`autoComplete`**
Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'age', // required
type: 'number', // required
required: true,
admin: {
step: 1,
},
},
],
}
```

View File

@@ -1,333 +0,0 @@
---
title: Fields Overview
label: Overview
order: 10
desc: Fields are the building blocks of Payload, find out how to add or remove a field, change field type, add hooks, define access control and validation.
keywords: overview, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner type="info">
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the
shape of the data that they store. Payload offers a wide variety of field types - both simple and
complex.
</Banner>
Fields are defined as an array on Collections and Globals via the `fields` key. They define the shape of the data that will be stored as well as automatically construct the corresponding Admin UI.
The required `type` property on a field determines what values it can accept, how it is presented in the API, and how the field will be rendered in the admin interface.
**Simple collection with two fields:**
```ts
import { CollectionConfig } from 'payload/types'
export const Page: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'myField',
type: 'text', // highlight-line
},
{
name: 'otherField',
type: 'checkbox', // highlight-line
},
],
}
```
### Field types
- [Array](/docs/fields/array) - for repeating content, supports nested fields
- [Blocks](/docs/fields/blocks) - block-based fields, allowing powerful layout creation
- [Checkbox](/docs/fields/checkbox) - boolean true / false checkbox
- [Code](/docs/fields/code) - code editor that saves a string to the database
- [Collapsible](/docs/fields/collapsible) - used for admin layout, nest fields within a collapsible component
- [Date](/docs/fields/date) - date / time field that saves a timestamp
- [Email](/docs/fields/email) - validates the entry is a properly formatted email
- [Group](/docs/fields/group) - nest fields within an object
- [JSON](/docs/fields/json) - saves actual JSON in the database
- [Number](/docs/fields/number) - field that enforces that its value be a number
- [Point](/docs/fields/point) - geometric coordinates for location data
- [Radio](/docs/fields/radio) - radio button group, allowing only one value to be selected
- [Relationship](/docs/fields/relationship) - assign relationships to other collections
- [Rich Text](/docs/fields/rich-text) - fully extensible Rich Text editor
- [Row](/docs/fields/row) - used for admin field layout, no effect on data shape
- [Select](/docs/fields/select) - dropdown / picklist style value selector
- [Tabs](/docs/fields/tabs) - used for admin layout, nest fields within tabs
- [Text](/docs/fields/text) - simple text input
- [Textarea](/docs/fields/textarea) - allows a bit larger of a text editor
- [Upload](/docs/fields/upload) - allows local file and image upload
- [UI](/docs/fields/ui) - inject your own custom components and do whatever you need
### Field-level hooks
One of the most powerful parts about Payload is its ability for you to define field-level hooks that can control the logic of your fields to a fine-grained level. for more information about how to define field hooks, [click here](/docs/hooks/overview#field-hooks).
### Field-level access control
In addition to being able to define access control on a document-level, you can define extremely granular permissions on a field by field level. For more information about field-level access control, [click here](/docs/access-control/overview#fields).
### Field names
All fields require a `name` property. This is the key that will be used to store and retrieve the field's value in the database. This property must be unique within the Collection, Global, or nested group that it is defined in.
Payload reserves various field names for internal use. Using reserved field names will result in your field being sanitized from the config.
The following field names are forbidden and cannot be used:
- `__v`
- `salt`
- `hash`
- `file`
### Validation
Field validation is enforced automatically based on the field type and other properties such as `required` or `min` and `max` value constraints on certain field types. This default behavior can be replaced by providing your own validate function for any field. It will be used on both the frontend and the backend, so it should not rely on any Node-specific packages. The validation function can be either synchronous or asynchronous and expects to return either `true` or a string error message to display in both API responses and within the Admin panel.
There are two arguments available to custom validation functions.
1. The value which is currently assigned to the field
2. An optional object with dynamic properties for more complex validation having the following:
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `data` | An object of the full collection or global document. |
| `siblingData` | An object of the document data limited to fields within the same parent to the field. |
| `operation` | Will be "create" or "update" depending on the UI action or API call. |
| `id` | The value of the collection `id`, will be `undefined` on create request. |
| `t` | The function for translating text, [more](/docs/configuration/i18n). |
| `user` | The currently authenticated user object. |
| `payload` | If the `validate` function is being executed on the server, Payload will be exposed for easily running local operations. |
Example:
```ts
import { CollectionConfig } from 'payload/types'
export const Orders: CollectionConfig = {
slug: 'orders',
fields: [
{
name: 'customerNumber',
type: 'text',
validate: async (val, { operation }) => {
if (operation !== 'create') {
// skip validation on update
return true
}
const response = await fetch(`https://your-api.com/customers/${val}`)
if (response.ok) {
return true
}
return 'The customer number provided does not match any customers within our records.'
},
},
],
}
```
When supplying a field `validate` function, Payload will use yours in place of the default. To make use of the default field validation in your custom logic you can import, call and return the result as needed.
For example:
```ts
import { text } from 'payload/fields/validations'
const field: Field = {
name: 'notBad',
type: 'text',
validate: (val, args) => {
if (val === 'bad') {
return 'This cannot be "bad"'
}
// highlight-start
return text(val, args)
// highlight-end
},
}
```
### Customizable ID
Collections ID fields are generated automatically by default. An explicit `id` field can be declared in the `fields` array to override this behavior.
Users are then required to provide a custom ID value when creating a record through the Admin UI or API.
Valid ID types are `number` and `text`.
When using the text value, remember that it shouldn't contain the / (slash) sign, as the API will read it separately and this can result in unexpected behavior.
Example:
```ts
{
fields: [
{
name: 'id',
type: 'number',
},
],
}
```
### Admin config
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `disableListColumn` | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| `disableListFilter` | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
### Custom components
All Payload fields support the ability to swap in your own React components with ease. For more information, including examples, [click here](/docs/admin/components#fields).
### Conditional logic
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes three arguments:
- `data` - the entire document's data that is currently being edited
- `siblingData` - only the fields that are direct siblings to the field with the condition
- `{ user }` - the final argument is an object containing the currently authenticated user
The `condition` function should return a boolean that will control if the field should be displayed or not.
**Example:**
```ts
{
fields: [
{
name: 'enableGreeting',
type: 'checkbox',
defaultValue: false,
},
{
name: 'greeting',
type: 'text',
admin: {
// highlight-start
condition: (data, siblingData, { user }) => {
if (data.enableGreeting) {
return true
} else {
return false
}
},
// highlight-end
},
},
]
}
```
### Default values
Fields can be prefilled with starting values using the `defaultValue` property. This is used in the admin UI and also on the backend as API requests will be populated with missing or undefined field values. You can assign the defaultValue directly in the field configuration or supply a function for dynamic behavior. Values assigned during a create request on the server are added before validation occurs.
Functions are called with an optional argument object containing:
- `user` - the authenticated user object
- `locale` - the currently selected locale string
Here is an example of a defaultValue function that uses both:
```ts
const translation: {
en: 'Written by'
es: 'Escrito por'
}
const field = {
name: 'attribution',
type: 'text',
// highlight-start
defaultValue: ({ user, locale }) => `${translation[locale]} ${user.name}`,
// highlight-end
}
```
<Banner type="success">
You can use async defaultValue functions to fill fields with data from API requests.
</Banner>
### Description
A description can be configured in three ways.
- As a string
- As a function which returns a string
- As a React component
Functions are called with an optional argument object with the following shape, and React components are rendered with the following props:
- `path` - the path of the field
- `value` - the current value of the field
As shown above, you can simply provide a string that will show by the field, but there are use cases where you may want to create some dynamic feedback. By using a function or a component for the `description` property you can provide realtime feedback as the user interacts with the form.
**Function Example:**
```ts
{
fields: [
{
name: 'message',
type: 'text',
maxLength: 20,
admin: {
description: ({ path, value }) =>
`${typeof value === 'string' ? 20 - value.length : '20'} characters left (field: ${path})`,
},
},
]
}
```
This example will display the number of characters allowed as the user types.
**Component Example:**
```ts
{
fields: [
{
name: 'message',
type: 'text',
maxLength: 20,
admin: {
description:
({ path, value }) => (
<div>
Character count:
{' '}
{ value?.length || 0 }
(field: {path})
</div>
)
}
}
]
}
```
This component will count the number of characters entered, as well as display the path of the field.
### TypeScript
You can import the internal Payload `Field` type as well as other common field types as follows:
```ts
import type { Field } from 'payload/types'
```

View File

@@ -1,74 +0,0 @@
---
title: Point Field
label: Point
order: 110
desc: The Point field type stores coordinates in the database. Learn how to use Point field for geolocation and geometry.
keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Point field type saves a pair of coordinates in the database and assigns an index for location
related queries.
</Banner>
<Banner type="warning">
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/point.png"
srcDark="https://payloadcms.com/images/docs/fields/point-dark.png"
alt="Shows a Point field in the Payload admin panel"
caption="Admin panel screenshot of a Point field"
/>
The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
### Config
| Option | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Used as a field label in the Admin panel and to name the generated GraphQL type. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
<Banner type="warning">
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
</Banner>
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'location',
type: 'point',
label: 'Location',
},
],
}
```
### Querying
In order to do query based on the distance to another point, you can use the `near` operator. When querying using the near operator, the returned documents will be sorted by nearest first.

View File

@@ -1,91 +0,0 @@
---
title: Radio Group Field
label: Radio Group
order: 120
desc: The Radio field type allows for the selection of one value from a predefined set of possible values. Learn how to use Radio fields, see examples and options.
keywords: radio, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Radio Group field type allows for the selection of one value from a predefined set of possible
values and presents a radio group-style set of inputs to the Admin panel.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/radio.png"
srcDark="https://payloadcms.com/images/docs/fields/radio-dark.png"
alt="Shows a Radio field in the Payload admin panel"
caption="Admin panel screenshot of a Radio field"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined.
_\* An asterisk denotes that a property is required._
<Banner type="warning">
<strong>Important:</strong>
<br />
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
</Banner>
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Radio Group field type allows for the specification of the following `admin` properties:
**`layout`**
The `layout` property allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'color', // required
type: 'radio', // required
options: [
// required
{
label: 'Mint',
value: 'mint',
},
{
label: 'Dark Gray',
value: 'dark_gray',
},
],
defaultValue: 'mint', // The first value in options.
admin: {
layout: 'horizontal',
},
},
],
}
```

View File

@@ -1,366 +0,0 @@
---
title: Relationship Field
label: Relationship
order: 130
desc: The Relationship field provides the ability to relate documents together. Learn how to use Relationship fields, see examples and options.
keywords: relationship, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Relationship field is one of the most powerful fields Payload features. It provides for the
ability to easily relate documents together.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/relationship.png"
srcDark="https://payloadcms.com/images/docs/fields/relationship-dark.png"
alt="Shows a relationship field in the Payload admin panel"
caption="Admin panel screenshot of a Relationship field"
/>
**Example uses:**
- To add `Product` documents to an `Order` document
- To allow for an `Order` to feature a `placedBy` relationship to either an `Organization` or `User` collection
- To assign `Category` documents to `Post` documents
### Config
| Option | Description |
|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
<Banner type="success">
<strong>Tip:</strong>
<br />
The [Depth](/docs/getting-started/concepts#depth) parameter can be used to automatically populate
related documents that are returned by the API.
</Banner>
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Relationship field type also
allows for the following admin-specific properties:
**`isSortable`**
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany`
is set to `true`).
**`allowCreate`**
Set to `false` if you'd like to disable the ability to create new documents from within the relationship field (hides
the "Add new" button in the admin UI).
**`sortOptions`**
The `sortOptions` property allows you to define a default sorting order for the options within a Relationship field's
dropdown. This can be particularly useful for ensuring that the most relevant options are presented first to the user.
You can specify `sortOptions` in two ways:
**As a string:**
Provide a string to define a global default sort field for all relationship field dropdowns across different
collections. You can prefix the field name with a minus symbol ("-") to sort in descending order.
Example:
```ts
sortOptions: 'fieldName',
```
This configuration will sort all relationship field dropdowns by `"fieldName"` in ascending order.
**As an object :**
Specify an object where keys are collection slugs and values are strings representing the field names to sort by. This
allows for different sorting fields for each collection's relationship dropdown.
Example:
```ts
sortOptions: {
"pages"
:
"fieldName1",
"posts"
:
"-fieldName2",
"categories"
:
"fieldName3"
}
```
In this configuration:
- Dropdowns related to `pages` will be sorted by `"fieldName1"` in ascending order.
- Dropdowns for `posts` will use `"fieldName2"` for sorting in descending order (noted by the "-" prefix).
- Dropdowns associated with `categories` will sort based on `"fieldName3"` in ascending order.
Note: If `sortOptions` is not defined, the default sorting behavior of the Relationship field dropdown will be used.
### Filtering relationship options
Options can be dynamically limited by supplying a [query constraint](/docs/queries/overview), which will be used both
for validating input and filtering available relationships in the UI.
The `filterOptions` property can either be a `Where` query, or a function returning `true` to not filter, `false` to
prevent all, or a `Where` query. When using a function, it will be
called with an argument object with the following properties:
| Property | Description |
|---------------|--------------------------------------------------------------------------------------|
| `relationTo` | The `relationTo` to filter against (as defined on the field) |
| `data` | An object of the full collection or global document currently being edited |
| `siblingData` | An object of the document data limited to fields within the same parent to the field |
| `id` | The value of the collection `id`, will be `undefined` on create request |
| `user` | The currently authenticated user object |
### Example
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'purchase',
type: 'relationship',
relationTo: ['products', 'services'],
filterOptions: ({ relationTo, siblingData }) => {
// returns a Where query dynamically by the type of relationship
if (relationTo === 'products') {
return {
stock: { greater_than: siblingData.quantity },
}
}
if (relationTo === 'services') {
return {
isAvailable: { equals: true },
}
}
},
},
],
}
```
You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning">
<strong>Note:</strong>
<br />
When a relationship field has both <strong>filterOptions</strong> and a custom{' '}
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
unless you call the default relationship field validation function imported from{' '}
<strong>payload/fields/validations</strong> in your validate function.
</Banner>
### How the data is saved
Given the variety of options possible within the `relationship` field type, the shape of the data needed for creating
and updating these fields can vary. The following sections will describe the variety of data shapes that can arise from
this field.
#### Has One
The most simple pattern of a relationship is to use `hasMany: false` with a `relationTo` that allows for only one type
of collection.
```ts
{
slug: 'example-collection',
fields
:
[
{
name: 'owner', // required
type: 'relationship', // required
relationTo: 'users', // required
hasMany: false,
}
]
}
```
The shape of the data to save for a document with the field configured this way would be:
```json
{
// ObjectID of the related user
"owner": "6031ac9e1289176380734024"
}
```
When querying documents in this collection via REST API, you could query as follows:
`?where[owner][equals]=6031ac9e1289176380734024`.
#### Has One - Polymorphic
Also known as **dynamic references**, in this configuration, the `relationTo` field is an array of Collection slugs that
tells Payload which Collections are valid to reference.
```ts
{
slug: 'example-collection',
fields
:
[
{
name: 'owner', // required
type: 'relationship', // required
relationTo: ['users', 'organizations'], // required
hasMany: false,
}
]
}
```
The shape of the data to save for a document with more than one relationship type would be:
```json
{
"owner": {
"relationTo": "organizations",
"value": "6031ac9e1289176380734024"
}
}
```
Here is an example for how to query documents by this data (note the difference in referencing the `owner.value`):
`?where[owner.value][equals]=6031ac9e1289176380734024`.
You can also query for documents where a field has a relationship to a specific Collection:
`?where[owners.relationTo][equals]=organizations`.
This query would return only documents that have an owner relationship to organizations.
#### Has Many
The `hasMany` tells Payload that there may be more than one collection saved to the field.
```ts
{
slug: 'example-collection',
fields
:
[
{
name: 'owners', // required
type: 'relationship', // required
relationTo: 'users', // required
hasMany: true,
}
]
}
```
To save the to `hasMany` relationship field we need to send an array of IDs:
```json
{
"owners": [
"6031ac9e1289176380734024",
"602c3c327b811235943ee12b"
]
}
```
When querying documents, the format does not change for arrays:
`?where[owners][equals]=6031ac9e1289176380734024`.
#### Has Many - Polymorphic
```ts
{
slug: 'example-collection',
fields
:
[
{
name: 'owners', // required
type: 'relationship', // required
relationTo: ['users', 'organizations'], // required
hasMany: true,
required: true,
}
]
}
```
Relationship fields with `hasMany` set to more than one kind of collections save their data as an array of objects—each
containing the Collection `slug` as the `relationTo` value, and the related document `id` for the `value`:
```json
{
"owners": [
{
"relationTo": "users",
"value": "6031ac9e1289176380734024"
},
{
"relationTo": "organizations",
"value": "602c3c327b811235943ee12b"
}
]
}
```
Querying is done in the same way as the earlier Polymorphic example:
`?where[owners.value][equals]=6031ac9e1289176380734024`.
#### Querying and Filtering Polymorphic Relationships
Polymorphic and non-polymorphic relationships must be queried differently because of how the related data is stored and
may be inconsistent across different collections. Because of this, filtering polymorphic relationship fields from the
Collection List admin UI is limited to the `id` value.
For a polymorphic relationship, the response will always be an array of objects. Each object will contain
the `relationTo` and `value` properties.
The data can be queried by the related document ID:
`?where[field.value][equals]=6031ac9e1289176380734024`.
Or by the related document Collection slug:
`?where[field.relationTo][equals]=your-collection-slug`.
However, you **cannot** query on any field values within the related document.
Since we are referencing multiple collections, the field you are querying on may not exist and break the query.
<Banner type="warning">
<strong>Note:</strong>
<br />
You <strong>cannot</strong> query on a field within a polymorphic relationship as you would with a non-polymorphic relationship.
</Banner>

View File

@@ -1,70 +0,0 @@
---
title: Rich Text Field
label: Rich Text
order: 140
desc: The Rich Text field allows dynamic content to be written through the Admin Panel. Learn how to use Rich Text fields, see examples and options.
keywords: rich text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Rich Text field is a powerful way to allow editors to write dynamic content. The content is
saved as JSON in the database and can be converted into any format, including HTML, that you need.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/richtext.png"
srcDark="https://payloadcms.com/images/docs/fields/richtext-dark.png"
alt="Shows a Rich Text field in the Payload admin panel"
caption="Admin panel screenshot of a Rich Text field"
/>
Payload's rich text field is built on an "adapter pattern" which lets you specify which rich text editor you'd like to use.
Right now, Payload is officially supporting two rich text editors:
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving
<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> rich text editor.</strong> Instead, you can invest your time and effort into learning the underlying open-source tools that will allow you to apply your learnings elsewhere as well.
</Banner>
### Config
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`editor`** | Override the rich text editor specified in your base configuration for this field. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Rich Text editor allows for the following admin properties:
**`placeholder`**
Set this property to define a placeholder string in the text input.
**`hideGutter`**
Set this property to `true` to hide this field's gutter within the admin panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
**`rtl`**
Override the default text direction of the Admin panel for this field. Set to `true` to force right-to-left text direction.
### Editor-specific options
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.

View File

@@ -1,65 +0,0 @@
---
title: Row Field
label: Row
order: 150
desc: With the Row field you can arrange fields next to each other in the Admin Panel to help you customize your Dashboard.
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Row field is presentational-only and only affects the Admin panel. By using it, you can
arrange fields next to each other horizontally.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/row.png"
srcDark="https://payloadcms.com/images/docs/fields/row-dark.png"
alt="Shows a row field in the Payload admin panel"
caption="Admin panel screenshot of a Row field"
/>
### Config
| Option | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`fields`** \* | Array of field types to nest within this Row. |
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
type: 'row', // required
fields: [
// required
{
name: 'label',
type: 'text',
required: true,
admin: {
width: '50%',
},
},
{
name: 'value',
type: 'text',
required: true,
admin: {
width: '50%',
},
},
],
},
],
}
```

View File

@@ -1,193 +0,0 @@
---
title: Select Field
label: Select
order: 160
desc: The Select field provides a dropdown-style interface for choosing options from a predefined list. Learn how to use Select fields, see examples and options.
keywords: select, multi-select, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Select field provides a dropdown-style interface for choosing options from a predefined list
as an enumeration.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/select.png"
srcDark="https://payloadcms.com/images/docs/fields/select-dark.png"
alt="Shows a Select field in the Payload admin panel"
caption="Admin panel screenshot of a Select field"
/>
### Config
| Option | Description |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
_\* An asterisk denotes that a property is required._
<Banner type="warning">
<strong>Important:</strong>
<br />
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
</Banner>
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Select field type also allows
for the following admin-specific properties:
**`isClearable`**
Set to `true` if you'd like this field to be clearable within the Admin UI.
**`isSortable`**
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works
when `hasMany` is set to `true`)
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'selectedFeatures', // required
type: 'select', // required
hasMany: true,
admin: {
isClearable: true,
isSortable: true, // use mouse to drag and drop different values, and sort them according to your choice
},
options: [
{
label: 'Metallic Paint',
value: 'metallic_paint',
},
{
label: 'Alloy Wheels',
value: 'alloy_wheels',
},
{
label: 'Carbon Fiber Dashboard',
value: 'carbon_fiber_dashboard',
},
],
},
],
}
```
### Customization
The Select field UI component can be customized by providing a custom React component to the `components` object in the
Base config.
```ts
export const CustomSelectField: Field = {
name: 'customSelectField',
type: 'select', // or 'text' if you have dynamic options
admin: {
components: {
Field: CustomSelectComponent({
options: [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
],
}),
},
}
}
```
You can import the existing Select component directly from Payload, then extend and customize it as needed.
```ts
import * as React from 'react';
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
type CustomSelectProps = {
path: string;
options: {
label: string;
value: string;
}[];
}
export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, options }) => {
const { value, setValue } = useField<string>({ path })
const { user } = useAuth()
const adjustedOptions = options.filter((option) => {
/*
A common use case for a custom select
is to show different options based on
the current user's role.
*/
return option;
});
return (
<div>
<label className = "field-label" >
Custom
Select
< /label>
< SelectInput
path = { path }
name = { path }
options = { adjustedOptions }
value = { value }
onChange = {(e)
=>
setValue(e.value)
}
/>
< /div>
)
}
```
If you are looking to create a dynamic select field, the following tutorial will walk you through the process of
creating a custom select field that fetches its options from an external API.
<VideoDrawer
id='Efn9OxSjA6Y'
label='How to Create a Custom Select Field'
drawerTitle='How to Create a Custom Select Field: A Step-by-Step Guide'
/>
If you want to learn more about custom components check out
the [Admin > Custom Component](/docs/admin/components#field-component) docs.

View File

@@ -1,87 +0,0 @@
---
title: Tabs Field
label: Tabs
order: 170
desc: The Tabs field is a great way to organize complex editing experiences into specific tab-based areas.
keywords: tabs, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Tabs field is presentational-only and only affects the Admin panel (unless a tab is named). By
using it, you can place fields within a nice layout component that separates certain sub-fields by
a tabbed interface.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/tabs.png"
srcDark="https://payloadcms.com/images/docs/fields/tabs-dark.png"
alt="Shows a tabs field used to separate Hero and Page layout in the Payload admin panel"
caption="Tabs field type used to separate Hero fields from Page Layout"
/>
### Config
| Option | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
#### Tab-specific Config
Each tab must have either a `name` or `label` and the required `fields` array. You can also optionally pass a `description` to render within each individual tab.
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
_\* An asterisk denotes that a property is required._
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
type: 'tabs', // required
tabs: [
// required
{
label: 'Tab One Label', // required
description: 'This will appear within the tab above the fields.',
fields: [
// required
{
name: 'someTextField',
type: 'text',
required: true,
},
],
},
{
name: 'tabTwo',
label: 'Tab Two Label', // required
interfaceName: 'TabTwo', // optional (`name` must be present)
fields: [
// required
{
name: 'numberField', // accessible via tabTwo.numberField
type: 'number',
required: true,
},
],
},
],
},
],
}
```

View File

@@ -1,79 +0,0 @@
---
title: Text Field
label: Text
order: 180
desc: Text field types simply save a string to the database and provide the Admin panel with a text input. Learn how to use Text fields, see examples and options.
keywords: text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Text field type is one of the most commonly used fields. It saves a string to the database and
provides the Admin panel with a simple text input.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/text.png"
srcDark="https://payloadcms.com/images/docs/fields/text-dark.png"
alt="Shows a text field and read-only text field in the Payload admin panel"
caption="Admin panel screenshot of a Text field and read-only Text field"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Text field type allows for the following `admin` properties:
**`placeholder`**
Set this property to define a placeholder string in the text input.
**`autoComplete`**
Set this property to a string that will be used for browser autocomplete.
**`rtl`**
Override the default text direction of the Admin panel for this field. Set to `true` to force right-to-left text direction.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageTitle', // required
type: 'text', // required
required: true,
},
],
}
```

View File

@@ -1,77 +0,0 @@
---
title: Textarea Field
label: Textarea
order: 190
desc: Textarea field types save a string to the database, similar to the Text field type but equipped for longer text. Learn how to use Textarea fields, see examples and options.
keywords: textarea, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Textarea field is almost identical to the Text field but it features a slightly larger input
that is better suited to edit longer text.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/textarea.png"
srcDark="https://payloadcms.com/images/docs/fields/textarea-dark.png"
alt="Shows a textarea field and read-only textarea field in the Payload admin panel"
caption="Admin panel screenshot of a Textarea field and read-only Textarea field"
/>
### Config
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Textarea field type allows for the following `admin` properties:
**`placeholder`**
Set this property to define a placeholder string in the textarea.
**`autoComplete`**
Set this property to a string that will be used for browser autocomplete.
**`rtl`**
Override the default text direction of the Admin panel for this field. Set to `true` to force right-to-left text direction.
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'metaDescription', // required
type: 'textarea', // required
required: true,
},
],
}
```

View File

@@ -1,61 +0,0 @@
---
title: UI Field
label: UI
order: 200
desc: UI fields are purely presentational and allow developers to customize the admin panel to a very fine degree, including adding actions and other functions.
keywords: custom field, react component, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The UI (user interface) field gives you a ton of power to add your own React components directly
into the Admin panel, nested directly within your other fields. It has absolutely no effect on the
data of your documents. It is presentational-only.
</Banner>
This field is helpful if you need to build in custom functionality via React components within the Admin panel. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
With this field, you can also inject custom `Cell` components that appear as additional columns within collections' List views.
**Example uses:**
- Add a custom message or block of text within the body of an Edit view to describe the purpose of surrounding fields
- Add a "Refund" button to an Order's Edit view sidebar, which might make a fetch call to a custom `refund` endpoint
- Add a "view page" button into a Pages List view to give editors a shortcut to view a page on the frontend of the site
- Build a "clear cache" button or similar mechanism to manually clear caches of specific documents
### Config
| Option | Description |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit view. [More](/docs/admin/components/#field-component) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'myCustomUIField', // required
type: 'ui', // required
admin: {
components: {
Field: MyCustomUIField,
Cell: MyCustomUICell,
},
},
},
],
}
```

View File

@@ -1,119 +0,0 @@
---
title: Upload Field
label: Upload
order: 210
desc: Upload fields will allow a file to be uploaded, only from a collection supporting Uploads. Learn how to use Upload fields, see examples and options.
keywords: upload, images media, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
The Upload field allows for the selection of a Document from a collection supporting Uploads, and
formats the selection as a thumbnail in the Admin panel.
</Banner>
<Banner type="warning">
<strong>Important:</strong>
<br />
To use this field, you need to have a Collection configured to allow Uploads. For more
information, [click here](/docs/upload/overview) to read about how to enable Uploads on a
collection by collection basis.
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/upload.png"
srcDark="https://payloadcms.com/images/docs/fields/upload-dark.png"
alt="Shows an upload field in the Payload admin panel"
caption="Admin panel screenshot of an Upload field"
/>
**Example uses:**
- To provide a `Page` with a featured image
- To allow for a `Product` to deliver a downloadable asset like PDF or MP3
- To give a layout building block the ability to feature a background image
### Config
| Option | Description |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`*relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. <strong>Note: the related collection must be configured to support Uploads.</strong> |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### Example
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'backgroundImage', // required
type: 'upload', // required
relationTo: 'media', // required
required: true,
},
],
}
```
### Filtering upload options
Options can be dynamically limited by supplying a [query constraint](/docs/queries/overview), which will be used both
for validating input and filtering available uploads in the UI.
The `filterOptions` property can either be a `Where` query, or a function returning `true` to not filter, `false` to
prevent all, or a `Where` query. When using a function, it will be
called with an argument object with the following properties:
| Property | Description |
|---------------|--------------------------------------------------------------------------------------|
| `relationTo` | The `relationTo` to filter against (as defined on the field) |
| `data` | An object of the full collection or global document currently being edited |
| `siblingData` | An object of the document data limited to fields within the same parent to the field |
| `id` | The value of the collection `id`, will be `undefined` on create request |
| `user` | The currently authenticated user object |
**Example:**
```ts
const uploadField = {
name: 'image',
type: 'upload',
relationTo: 'media',
filterOptions: {
mimeType: { contains: 'image' },
},
}
```
You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning">
<strong>Note:</strong>
<br />
When an upload field has both <strong>filterOptions</strong> and a custom{' '}
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
unless you call the default upload field validation function imported from{' '}
<strong>payload/fields/validations</strong> in your validate function.
</Banner>

View File

@@ -1,164 +0,0 @@
---
title: Payload Concepts
label: Concepts
order: 20
desc: Payload is based around a small and intuitive set of concepts. Key concepts include collections, globals, fields and more.
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, express
---
Payload is based around a small and intuitive set of concepts. Before starting to work with Payload, it's a good idea to familiarize yourself with the following:
### Config
<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.
### Collections
<Banner type="info">
A Collection represents a type of content that Payload will store and can contain many documents.
</Banner>
Collections define the shape of your data as well as all functionalities attached to that data. They will contain one or many "documents", all corresponding with the same fields and functionalities that you define.
They can represent anything you can store in a database - for example - pages, posts, users, people, orders, categories, events, customers, transactions, and anything else your app needs.
### Globals
<Banner type="info">
A Global is a "one-off" piece of content that is perfect for storing navigational structures,
themes, top-level meta data, and more.
</Banner>
Globals are in many ways similar to Collections, but there is only ever **one** instance of a Global, whereas Collections can contain many documents.
### Fields
<Banner type="info">
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the
shape of the data that they store.
</Banner>
Payload comes with [many different field types](../fields/overview) that give you a ton of flexibility while designing your API. Each Field type has its own potential properties that allow you to customize how they work.
### Hooks
<Banner type="info">
Hooks are where you can "tie in" to existing Payload actions to perform your own additional logic
or modify how Payload operates altogether.
</Banner>
Hooks are an extremely powerful concept and are central to extending and customizing your app. Payload provides a wide variety of hooks which you can utilize. For example, imagine if you'd like to send an email every time a document is created in your Orders collection. To do so, you can add an `afterChange` hook function to your Orders collection that receives the Order data and allows you to send an email accordingly.
There are many more potential reasons to use Hooks. For more, visit the [Hooks documentation](/docs/hooks/overview).
### Access Control
<Banner type="info">
Access Control refers to Payload's system of defining who can do what to your API.
</Banner>
Access Control is extremely powerful but easy and intuitive to manage. You can easily define your own full-blown RBAC (role-based access control) or any other access control pattern that your scenario requires. No conventions or structure is forced on you whatsoever.
For more, visit the [Access Control documentation](/docs/access-control/overview).
### Depth
<Banner type="info">
"Depth" gives you control over how many levels down related documents should be automatically
populated when retrieved.
</Banner>
You can specify population `depth` via query parameter in the REST API and by an option in the local API. _Depth has no effect in the GraphQL API, because there, depth is based on the shape of your queries._
It is also possible to limit the depth for specific `relation` and `upload` fields using the `maxDepth` property in your configuration.
**For example, let's look at the following Collections:** `departments`, `users`, `posts`
```
// type: 'relationship' fields are equal to 1 depth level
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'author',
label: 'Post Author',
type: 'relationship',
relationTo: 'users',
}
]
}
{
slug: 'users',
fields: [
{
name: 'email',
type: 'email',
},
{
name: 'department'
type: 'relationship',
relationTo: 'departments'
}
]
}
{
slug: 'departments',
fields: [
{
name: 'name'
type: 'text',
}
]
}
```
If you were to query the Posts endpoint at, say, `http://localhost:3000/api/posts?depth=1`, you will retrieve Posts with populations one level deep. This depth parameter can be thought of as N, where N is the number of levels you want to populate. To populate one level further, you would simply specify N+1 as the depth. A returned result may look like the following:
```
// ?depth=1
{
id: '5ae8f9bde69e394e717c8832',
title: 'This post sucks',
author: {
id: '5f7dd05cd50d4005f8bcab17',
email: 'spiderman@superheroes.gov',
department: '5e3ca05cd50d4005f8bdab15'
}
}
```
Notice how the `user.author` is fully populated, but `user.author.department` is left as a document ID? That's because the User collection counted as the first level of `depth` and got populated—but then prevented any further populations from taking place.
To populate `user.author.department` in it's entirety you could specify `?depth=2` or _higher_.
```
// ?depth=2
{
id: '5ae8f9bde69e394e717c8832',
title: 'This post sucks',
author: {
id: '5f7dd05cd50d4005f8bcab17',
email: 'spiderman@superheroes.gov',
department: {
id: '5e3ca05cd50d4005f8bdab15',
name: 'Marvel'
}
}
}
```
<Banner type="warning">
<strong>Note:</strong>
<br />
When access control on collections prevents relationship fields from populating, the API response
will contain the relationship id instead of the full document.
</Banner>

View File

@@ -1,155 +0,0 @@
---
title: Installation
label: Installation
order: 30
desc: To quickly get started with Payload, simply run npx create-payload-app or install from scratch.
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, express
---
#### Software Requirements
Payload requires the following software:
- Any JavaScript package manager (Yarn, NPM, or pnpm)
- Node.js version 16+
- Any [compatible database](/docs/database/overview) (MongoDB or Postgres)
<Banner type="warning">
Before proceeding any further, please ensure that you have the above requirements met.
</Banner>
## Quickstart with create-payload-app
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
```
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 + Express app is super straightforward. To add to an existing app, just run `npm install --save --legacy-peer-deps payload`.
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:
```js
import { buildConfig } from 'payload/config'
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!
})
```
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.
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
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."
)
})
}
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:
```
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 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).

View File

@@ -1,65 +0,0 @@
---
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, express
---
<YouTube
id="In_lFhzmbME"
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.
<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>.
</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:
- 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
## What does "headless" mean?
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.
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.
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.
### Why Payload?
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.
**We believe that a CMS should be:**
- 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
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:
- 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

View File

@@ -1,156 +0,0 @@
---
title: Adding your own Queries and Mutations
label: Custom Queries and Mutations
order: 20
desc: Payload allows you to add your own GraphQL queries and mutations, simply set up GraphQL in your main Payload config by following these instructions.
keywords: graphql, resolvers, mutations, custom, queries, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
You can add your own GraphQL queries and mutations to Payload, making use of all the types that Payload has defined for you.
To do so, add your queries and mutations to the main Payload config as follows:
| Config Path | Description |
| ------------------- | --------------------------------------------------------------------------- |
| `graphQL.queries` | Function that returns an object containing keys to custom GraphQL queries |
| `graphQL.mutations` | Function that returns an object containing keys to custom GraphQL mutations |
The above properties each receive a function that is defined with the following arguments:
**`GraphQL`**
This is Payload's GraphQL dependency. You should not install your own copy of GraphQL as a dependency due to underlying restrictions based on how GraphQL works. Instead, you can use the Payload-provided copy via this argument.
**`payload`**
This is a copy of the currently running Payload instance, which provides you with existing GraphQL types for all of your Collections and Globals - among other things.
##### Return value
Both `graphQL.queries` and `graphQL.mutations` functions should return an object with properties equal to your newly written GraphQL queries and mutations.
### Example
`payload.config.js`:
```ts
import { buildConfig } from 'payload/config'
import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver'
export default buildConfig({
graphQL: {
// highlight-start
queries: (GraphQL, payload) => {
return {
MyCustomQuery: {
type: new GraphQL.GraphQLObjectType({
name: 'MyCustomQuery',
fields: {
text: {
type: GraphQL.GraphQLString,
},
someNumberField: {
type: GraphQL.GraphQLFloat,
},
},
}),
args: {
argNameHere: {
type: new GraphQL.GraphQLNonNull(GraphQLString),
},
},
resolve: myCustomQueryResolver,
},
}
},
// highlight-end
},
})
```
### Resolver function
In your resolver, make sure you set `depth: 0` if you're returning data directly from the local API so that GraphQL can correctly resolve queries to nested values such as relationship data.
Your function will receive four arguments you can make use of:
Example
```ts
;async (obj, args, context, info) => {}
```
**`obj`**
The previous object. Not very often used and usually discarded.
**`args`**
The available arguments from your query or mutation will be available to you here, these must be configured via the custom operation first.
**`context`**
An object containing the `req` and `res` objects that will provide you with the `payload`, `user` instances and more, like any other Payload API handler.
**`info`**
Contextual information about the currently running GraphQL operation. You can get schema information from this as well as contextual information about where this resolver function is being run.
### Types
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/).
**`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.
**`buildPaginatedListType`**
This is a utility function that allows you to build a new GraphQL type for a paginated result similar to the Payload's generated schema.
It takes in two arguments, the first for the name of this new schema type and the second for the GraphQL type to be used in the docs parameter.
Example
```ts
export const getMyPosts = (GraphQL, payload) => {
return {
args: {},
resolve: Resolver,
// The name of your new type has to be unique
type: buildPaginatedListType('AuthorPosts', payload.collections['posts'].graphQL?.type),
}
}
```
**`payload.collections.slug.graphQL`**
If you want to extend more of the provided API then the `graphQL` object on your collection slug will contain additional types to help you re-use code for types, mutations and queries.
```ts
graphQL?: {
type: GraphQLObjectType
paginatedType: GraphQLObjectType
JWT: GraphQLObjectType
versionType: GraphQLObjectType
whereInputType: GraphQLInputObjectType
mutationInputType: GraphQLNonNull<any>
updateMutationInputType: GraphQLNonNull<any>
}
```
### Best practices
There are a few ways to structure your code, we recommend using a dedicated `graphql` directory so you can keep all of your logic in one place. You have total freedom of how you want to structure this but a common pattern is to group functions by type and with their resolver.
Example
```
src/graphql
---- queries/
index.ts
-- myCustomQuery/
index.ts
resolver.ts
---- mutations/
```

View File

@@ -1,92 +0,0 @@
---
title: GraphQL Schema
label: GraphQL Schema
order: 30
desc: Output your own GraphQL schema based on your collections and globals to a file.
keywords: headless cms, typescript, documentation, Content Management System, cms, headless, javascript, node, react, express
---
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.
### Schema generation script
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:
```
payload generate:graphQLSchema
```
You can run this command whenever you need to regenerate your GraphQL schema and output it to a file, and then you can use the schema for writing your own GraphQL elsewhere in other projects.
### Custom output file path
```js
{
// the remainder of your config
graphQL: {
schemaOutputFile: path.resolve(__dirname, './graphql/schema.graphql'),
},
}
```
### Custom Field Schemas
For `array`, `block`, `group` and named `tab` fields, you can generate top level reusable interfaces. The following group field config:
```ts
{
type: 'group',
name: 'meta',
interfaceName: 'SharedMeta', <-- here!!
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'text',
},
],
}
```
will generate:
```ts
// a top level reusable type!!
type SharedMeta {
title: String
description: String
}
// example usage inside collection schema
type Collection1 {
// ...other fields
meta: SharedMeta
}
```
The above example outputs all your definitions to a file relative from your payload config as `./graphql/schema.graphql`. By default, the file will be output to your current working directory as `schema.graphql`.
#### Adding an NPM script
<Banner type="warning">
<strong>Important</strong>
<br />
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.
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:
```json
{
"scripts": {
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
}
}
```
Now you can run `yarn generate:graphQLSchema` to easily generate your schema.

View File

@@ -1,130 +0,0 @@
---
title: GraphQL Overview
label: Overview
order: 10
desc: Payload ships with a fully featured and extensible GraphQL API, which can be used in addition to the REST and Local APIs to give you more flexibility.
keywords: graphql, resolvers, mutations, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
In addition to its REST and Local APIs, Payload ships with a fully featured and extensible GraphQL API.
By default, the GraphQL API is exposed via `/api/graphql`, but you can customize this URL via specifying your `routes` within the main Payload config.
The labels you provide for your Collections and Globals are used to name the GraphQL types that are created to correspond to your config. Special characters and spaces are removed.
## GraphQL Options
At the top of your Payload config you can define all the options to manage GraphQL.
| Option | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `schemaOutputFile` | A string for the file path used by the generate schema command. Defaults to `graphql.schema` next to `payload.config.ts` [More](/docs/graphql/graphql-schema) |
## Collections
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';
export const PublicUser: CollectionConfig = {
slug: 'public-users',
auth: true, // Auth is enabled
fields: [
...
],
}
```
**Payload will automatically open up the following queries:**
| Query Name | 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 |
## Globals
Globals are also fully supported. For example:
```ts
import { GlobalConfig } from 'payload/types';
const Header: GlobalConfig = {
slug: 'header',
fields: [
...
],
}
```
**Payload will open the following query:**
| Query Name | Operation |
| ------------ | --------- |
| **`Header`** | `findOne` |
**And the following mutation:**
| Query Name | Operation |
| ------------------ | --------- |
| **`updateHeader`** | `update` |
## Preferences
User [preferences](/docs/admin/overview#preferences) for the admin panel are also available to GraphQL the same way as other collection schemas are generated. To query preferences you must supply an authorization token in the header and only the preferences of that user will be accessible.
**Payload will open the following query:**
| Query Name | Operation |
| ---------------- | --------- |
| **`Preference`** | `findOne` |
**And the following mutations:**
| Query Name | Operation |
| ---------------------- | --------- |
| **`updatePreference`** | `update` |
| **`deletePreference`** | `delete` |
## GraphQL Playground
GraphQL Playground is enabled by default for development purposes, but disabled in production. You can enable it in production by passing `graphQL.disablePlaygroundInProduction` a `false` setting in the main Payload config.
You can even log in using the `login[collection-singular-label-here]` mutation to use the Playground as an authenticated user.
<Banner type="success">
<strong>Tip:</strong>
<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))
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>
## Query complexity limits
Payload comes with a built-in query complexity limiter to prevent bad people from trying to slow down your server by running massive queries. To learn more, [click here](/docs/production/preventing-abuse#limiting-graphql-complexity).

View File

@@ -1,355 +0,0 @@
---
title: Collection Hooks
label: Collections
order: 20
desc: You can add hooks to any Collection, several hook types are available including beforeChange, afterRead, afterDelete and more.
keywords: hooks, collections, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Collections feature the ability to define the following hooks:
- [beforeOperation](#beforeoperation)
- [beforeValidate](#beforevalidate)
- [beforeChange](#beforechange)
- [afterChange](#afterchange)
- [beforeRead](#beforeread)
- [afterRead](#afterread)
- [beforeDelete](#beforedelete)
- [afterDelete](#afterdelete)
- [afterOperation](#afteroperation)
Additionally, `auth`-enabled collections feature the following hooks:
- [beforeLogin](#beforelogin)
- [afterLogin](#afterlogin)
- [afterLogout](#afterlogout)
- [afterRefresh](#afterrefresh)
- [afterMe](#afterme)
- [afterForgotPassword](#afterforgotpassword)
- [refresh](#refresh)
- [me](#me)
## Config
All collection Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`collections/exampleHooks.js`
```ts
import { CollectionConfig } from 'payload/types';
export const ExampleHooks: CollectionConfig = {
slug: 'example-hooks',
fields: [
{ name: 'name', type: 'text'},
],
hooks: {
beforeOperation: [(args) => {...}],
beforeValidate: [(args) => {...}],
beforeDelete: [(args) => {...}],
beforeChange: [(args) => {...}],
beforeRead: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
afterDelete: [(args) => {...}],
afterOperation: [(args) => {...}],
// Auth-enabled hooks
beforeLogin: [(args) => {...}],
afterLogin: [(args) => {...}],
afterLogout: [(args) => {...}],
afterRefresh: [(args) => {...}],
afterMe: [(args) => {...}],
afterForgotPassword: [(args) => {...}],
refresh: [(args) => {...}],
me: [(args) => {...}],
},
}
```
### beforeOperation
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`.
```ts
import { CollectionBeforeOperationHook } from 'payload/types'
const beforeOperationHook: CollectionBeforeOperationHook = async ({
args, // original arguments passed into the operation
operation, // name of the operation
req, // full express request
}) => {
return args // return modified operation arguments as necessary
}
```
### beforeValidate
Runs before the `create` and `update` operations. This hook allows you to add or format data before the incoming data is validated server-side.
Please do note that this does not run before the client-side validation. If you added a `validate` function, this would be the lifecycle:
1. `validate` runs on the client
2. if successful, `beforeValidate` runs on the server
3. `validate` runs on the server
```ts
import { CollectionBeforeValidateHook } from 'payload/types'
const beforeValidateHook: CollectionBeforeValidateHook = async ({
data, // incoming data to update or create with
req, // full express request
operation, // name of the operation ie. 'create', 'update'
originalDoc, // original document
}) => {
return data // Return data to either create or update a document with
}
```
### beforeChange
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'
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data, // incoming data to update or create with
req, // full express request
operation, // name of the operation ie. 'create', 'update'
originalDoc, // original document
}) => {
return data // Return data to either create or update a document with
}
```
### afterChange
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'
const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // full document data
req, // full express request
previousDoc, // document data before updating the collection
operation, // name of the operation ie. 'create', 'update'
}) => {
return doc
}
```
### beforeRead
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'
const beforeReadHook: CollectionBeforeReadHook = async ({
doc, // full document data
req, // full express request
query, // JSON formatted query
}) => {
return doc
}
```
### afterRead
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'
const afterReadHook: CollectionAfterReadHook = async ({
doc, // full document data
req, // full express request
query, // JSON formatted query
findMany, // boolean to denote if this hook is running against finding one, or finding many
}) => {
return doc
}
```
### beforeDelete
Runs before the `delete` operation. Returned values are discarded.
```ts
import { CollectionBeforeDeleteHook } from 'payload/types';
const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
req, // full express request
id, // id of document to delete
}) => {...}
```
### afterDelete
Runs immediately after the `delete` operation removes records from the database. Returned values are discarded.
```ts
import { CollectionAfterDeleteHook } from 'payload/types';
const afterDeleteHook: CollectionAfterDeleteHook = async ({
req, // full express request
id, // id of document to delete
doc, // deleted document
}) => {...}
```
### afterOperation
The `afterOperation` hook can be used to modify the result of operations or execute side-effects that run after an operation has completed.
Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`.
```ts
import { CollectionAfterOperationHook } from 'payload/types'
const afterOperationHook: CollectionAfterOperationHook = async ({
args, // arguments passed into the operation
operation, // name of the operation
req, // full express request
result, // the result of the operation, before modifications
}) => {
return result // return modified result as necessary
}
```
### beforeLogin
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'
const beforeLoginHook: CollectionBeforeLoginHook = async ({
req, // full express request
user, // user being logged in
}) => {
return user
}
```
### afterLogin
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';
const afterLoginHook: CollectionAfterLoginHook = async ({
req, // full express request
user, // user that was logged in
token, // user token
}) => {...}
```
### afterLogout
For auth-enabled Collections, this hook runs after `logout` operations.
```ts
import { CollectionAfterLogoutHook } from 'payload/types';
const afterLogoutHook: CollectionAfterLogoutHook = async ({
req, // full express request
}) => {...}
```
### afterRefresh
For auth-enabled Collections, this hook runs after `refresh` operations.
```ts
import { CollectionAfterRefreshHook } from 'payload/types';
const afterRefreshHook: CollectionAfterRefreshHook = async ({
req, // full express request
res, // full express response
token, // newly refreshed user token
}) => {...}
```
### afterMe
For auth-enabled Collections, this hook runs after `me` operations.
```ts
import { CollectionAfterMeHook } from 'payload/types';
const afterMeHook: CollectionAfterMeHook = async ({
req, // full express request
response, // response to return
}) => {...}
```
### afterForgotPassword
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
```ts
import { CollectionAfterForgotPasswordHook } from 'payload/types'
const afterForgotPasswordHook: CollectionAfterForgotPasswordHook = async ({
args, // arguments passed into the operation
context,
collection, // The collection which this hook is being run on
}) => {...}
```
### refresh
For auth-enabled Collections, this hook allows you to optionally replace the default behavior of the `refresh` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue.
```ts
import type { CollectionRefreshHook } from 'payload/types'
const myRefreshHook: CollectionRefreshHook = async ({
args, // arguments passed into the `refresh` operation
user, // the user as queried from the database
}) => {...}
```
### me
For auth-enabled Collections, this hook allows you to optionally replace the default behavior of the `me` operation with your own. If you optionally return a value from your hook, the operation will not perform its own logic and continue.
```ts
import type { CollectionMeHook } from 'payload/types'
const meHook: CollectionMeHook = async ({
args, // arguments passed into the `me` operation
user, // the user as queried from the database
}) => {...}
```
## TypeScript
Payload exports a type for each Collection hook which can be accessed as follows:
```ts
import type {
CollectionBeforeOperationHook,
CollectionBeforeValidateHook,
CollectionBeforeChangeHook,
CollectionAfterChangeHook,
CollectionAfterReadHook,
CollectionBeforeReadHook,
CollectionBeforeDeleteHook,
CollectionAfterDeleteHook,
CollectionBeforeLoginHook,
CollectionAfterLoginHook,
CollectionAfterLogoutHook,
CollectionAfterRefreshHook,
CollectionAfterMeHook,
CollectionAfterForgotPasswordHook,
CollectionRefreshHook,
CollectionMeHook,
} from 'payload/types'
```

View File

@@ -1,141 +0,0 @@
---
title: Context
label: Context
order: 50
desc: Context allows you to pass in extra data that can be shared between hooks
keywords: hooks, context, payload context, payloadcontext, data, extra data, shared data, shared, extra
---
The `context` object in hooks is used to share data across different hooks. The persists throughout the entire lifecycle of a request and is available within every hook. This allows you to add logic to your hooks based on the request state by setting properties to `req.context` and using them elsewhere.
## When to use Context
Context gives you a way forward on otherwise difficult problems such as:
1. **Passing data between hooks**: Needing data in multiple hooks from a 3rd party API, it could be retrieved and used in `beforeChange` and later used again in an `afterChange` hook without having to fetch it twice.
2. **Preventing infinite loops**: Calling `payload.update()` on the same document that triggered an `afterChange` hook will create an infinite loop, control the flow by assigning a no-op condition to context
3. **Passing data to local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
4. **Passing data between hooks and middleware or custom endpoints**: Hooks could set context across multiple collections and then be used in a final `postMiddleware`.
## How to Use Context
Let's see examples on how context can be used in the first two scenarios mentioned above:
### Passing data between hooks
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
For example:
```ts
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
beforeChange: [
async ({ context, data }) => {
// assign the customerData to context for use later
context.customerData = await fetchCustomerData(data.customerID)
return {
...data,
// some data we use here
name: context.customerData.name,
}
},
],
afterChange: [
async ({ context, doc, req }) => {
// use context.customerData without needing to fetch it again
if (context.customerData.contacted === false) {
createTodo('Call Customer', context.customerData)
}
},
],
},
fields: [
/* ... */
],
}
```
### Preventing infinite loops
Let's say you have an `afterChange` hook, and you want to do a calculation inside the hook (as the document ID needed for the calculation is available in the `afterChange` hook, but not in the `beforeChange` hook). Once that's done, you want to update the document with the result of the calculation.
Bad example:
```ts
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
afterChange: [
async ({ doc }) => {
await payload.update({
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
collection: 'customers',
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
})
},
],
},
fields: [
/* ... */
],
}
```
Instead of the above, we need to tell the `afterChange` hook to not run again if it performs the update (and thus not update itself again). We can solve that with context.
Fixed example:
```ts
const MyCollection: CollectionConfig = {
slug: 'slug',
hooks: {
afterChange: [
async ({ context, doc }) => {
// return if flag was previously set
if (context.triggerAfterChange === false) {
return
}
await payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
context: {
// set a flag to prevent from running again
triggerAfterChange: false,
},
})
},
],
},
fields: [
/* ... */
],
}
```
## Typing context
The default typescript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax.
This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file:
```ts
import { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
myObject?: string
// ...
}
}
```
This will add a the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.

View File

@@ -1,238 +0,0 @@
---
title: Field Hooks
label: Fields
order: 30
desc: Hooks can be added to any fields, and optionally modify the return value of the field before the operation continues.
keywords: hooks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Field-level hooks offer incredible potential for encapsulating your logic. They help to isolate concerns and package up
functionalities to be easily reusable across your projects.
**Example use cases include:**
- Automatically add an `owner` relationship to a Document based on the `req.user.id`
- Encrypt / decrypt a sensitive field using `beforeValidate` and `afterRead` hooks
- Auto-generate field data using a `beforeValidate` hook
- Format incoming data such as kebab-casing a document `slug` with `beforeValidate`
- Restrict updating a document to only once every X hours using the `beforeChange` hook
**All field types provide the following hooks:**
- [beforeValidate](#beforevalidate)
- [beforeChange](#beforechange)
- [afterChange](#afterchange)
- [afterRead](#afterread)
## Config
Example field configuration:
```ts
import { Field } from 'payload/types';
const ExampleField: Field = {
name: 'name',
type: 'text',
// highlight-start
hooks: {
beforeValidate: [(args) => {...}],
beforeChange: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
}
// highlight-end
}
```
## Arguments and return values
All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on
which field hook you are utilizing.
<Banner type="success">
<strong>Tip:</strong>
<br />
It's a good idea to conditionally scope your logic based on which operation is executing. For
example, if you are writing a <strong>beforeChange</strong> hook, you may want to perform
different logic based on if the current <strong>operation</strong> is <strong>create</strong> or{' '}
<strong>update</strong>.
</Banner>
#### Arguments
Field Hooks receive one `args` argument that contains the following properties:
| Option | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. |
| **`siblingData`** | The sibling data passed to a field that the hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. |
| **`previousDoc`** | The document before changes were applied, only in `afterChange` hooks. |
| **`previousSiblingDoc`** | The sibling data of the document before changes being applied, only in `beforeChange` and `afterChange` hook. |
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
| **`value`** | The value of the field. |
| **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. |
| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) |
| **`field`** | The field which the hook is running against. |
| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. |
| **`global`** | The global which the field belongs to. If the field belongs to a collection, this will be null. |
#### Return value
All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may
optionally return the value that should be used within the field.
<Banner type="warning">
<strong>Important</strong>
<br />
Due to GraphQL's typed nature, you should never change the type of data that you return from a
field, otherwise GraphQL will produce errors. If you need to change the shape or type of data,
reconsider Field Hooks and instead evaluate if Collection / Global hooks might suit you better.
</Banner>
## Examples of Field Hooks
To better illustrate how field-level hooks can be applied, here are some specific examples. These demonstrate the
flexibility and potential of field hooks in different contexts. Remember, these examples are just a starting point - the
true potential of field-level hooks lies in their adaptability to a wide array of use cases.
### beforeValidate
Runs before the `update` operation. This hook allows you to pre-process or format field data before it undergoes
validation.
```ts
import { Field } from 'payload/types'
const usernameField: Field = {
name: 'username',
type: 'text',
hooks: {
beforeValidate: [({ value }) => {
// Trim whitespace and convert to lowercase
return value.trim().toLowerCase()
}],
}
}
```
In this example, the `beforeValidate` hook is used to process the `username` field. The hook takes the incoming value of
the field and transforms it by trimming whitespace and converting it to lowercase. This ensures that the username is
stored in a consistent format in the database.
### beforeChange
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage,
you can be confident that the field data that will be saved to the document is valid in accordance to your field
validations.
```ts
import { Field } from 'payload/types'
const emailField: Field = {
name: 'email',
type: 'email',
hooks: {
beforeChange: [({ value, operation }) => {
if (operation === 'create') {
// Perform additional validation or transformation for 'create' operation
}
return value
}],
}
}
```
In the `emailField`, the `beforeChange` hook checks the `operation` type. If the operation is `create`, it performs
additional validation or transformation on the email field value. This allows for operation-specific logic to be applied
to the field.
### afterChange
The `afterChange` hook is executed after a field's value has been changed and saved in the database. This hook is useful
for post-processing or triggering side effects based on the new value of the field.
```ts
import { Field } from 'payload/types'
const membershipStatusField: Field = {
name: 'membershipStatus',
type: 'select',
options: [
{ label: 'Standard', value: 'standard' },
{ label: 'Premium', value: 'premium' },
{ label: 'VIP', value: 'vip' }
],
hooks: {
afterChange: [({ value, previousValue, req }) => {
if (value !== previousValue) {
// Log or perform an action when the membership status changes
console.log(`User ID ${req.user.id} changed their membership status from ${previousValue} to ${value}.`)
// Here, you can implement actions that could track conversions from one tier to another
}
}],
}
}
```
In this example, the `afterChange` hook is used with a `membershipStatusField`, which allows users to select their
membership level (Standard, Premium, VIP). The hook monitors changes in the membership status. When a change occurs, it
logs the update and can be used to trigger further actions, such as tracking conversion from one tier to another or
notifying them about changes in their membership benefits.
### afterRead
The `afterRead` hook is invoked after a field value is read from the database. This is ideal for formatting or
transforming the field data for output.
```ts
import { Field } from 'payload/types'
const dateField: Field = {
name: 'createdAt',
type: 'date',
hooks: {
afterRead: [({ value }) => {
// Format date for display
return new Date(value).toLocaleDateString()
}],
}
}
```
Here, the `afterRead` hook for the `dateField` is used to format the date into a more readable format
using `toLocaleDateString()`. This hook modifies the way the date is presented to the user, making it more
user-friendly.
## TypeScript
Payload exports a type for field hooks which can be accessed and used as follows:
```ts
import type { FieldHook } from 'payload/types'
// Field hook type is a generic that takes three arguments:
// 1: The document type
// 2: The value type
// 3: The sibling data type
type ExampleFieldHook = FieldHook<ExampleDocumentType, string, SiblingDataType>
const exampleFieldHook: ExampleFieldHook = (args) => {
const {
value, // Typed as `string` as shown above
data, // Typed as a Partial of your ExampleDocumentType
siblingData, // Typed as a Partial of SiblingDataType
originalDoc, // Typed as ExampleDocumentType
operation,
req,
} = args
// Do something here...
return value // should return a string as typed above, undefined, or null
}
```

View File

@@ -1,128 +0,0 @@
---
title: Global Hooks
label: Globals
order: 40
desc: Hooks can be added to any Global and allow you to validate data, flatten locales, hide protected fields, remove fields and more.
keywords: hooks, globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Globals feature the ability to define the following hooks:
- [beforeValidate](#beforevalidate)
- [beforeChange](#beforechange)
- [afterChange](#afterchange)
- [beforeRead](#beforeread)
- [afterRead](#afterread)
## Config
All Global Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`globals/example-hooks.js`
```ts
import { GlobalConfig } from 'payload/types';
const ExampleHooks: GlobalConfig = {
slug: 'header',
fields: [
{ name: 'title', type: 'text'},
]
hooks: {
beforeValidate: [(args) => {...}],
beforeChange: [(args) => {...}],
beforeRead: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
}
}
```
### beforeValidate
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'
const beforeValidateHook: GlobalBeforeValidateHook = async ({
data, // incoming data to update or create with
req, // full express request
originalDoc, // original document
}) => {
return data // Return data to update the document with
}
```
### beforeChange
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'
const beforeChangeHook: GlobalBeforeChangeHook = async ({
data, // incoming data to update or create with
req, // full express request
originalDoc, // original document
}) => {
return data // Return data to update the document with
}
```
### afterChange
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'
const afterChangeHook: GlobalAfterChangeHook = async ({
doc, // full document data
previousDoc, // document data before updating the global
req, // full express request
}) => {
return data
}
```
### beforeRead
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'
const beforeReadHook: GlobalBeforeReadHook = async ({
doc, // full document data
req, // full express request
}) => {...}
```
### afterRead
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'
const afterReadHook: GlobalAfterReadHook = async ({
doc, // full document data
req, // full express request
findMany, // boolean to denote if this hook is running against finding one, or finding many (useful in versions)
}) => {...}
```
## TypeScript
Payload exports a type for each Global hook which can be accessed as follows:
```ts
import type {
GlobalBeforeValidateHook,
GlobalBeforeChangeHook,
GlobalAfterChangeHook,
GlobalBeforeReadHook,
GlobalAfterReadHook,
} from 'payload/types'
```

View File

@@ -1,47 +0,0 @@
---
title: Hooks Overview
label: Overview
order: 10
desc: Hooks allow you to add your own logic to Payload, including integrating with third-party APIs, adding auto-generated data, or modifying Payload's base functionality.
keywords: hooks, overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner type="info">
Hooks are powerful ways to tie into existing Payload actions in order to add your own logic like
integrating with third-party APIs, adding auto-generated data, or modifying Payload's base
functionality.
</Banner>
**With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework.**
Example uses:
- Integrate user profiles with a third-party CRM such as Salesforce or Hubspot
- Send a copy of uploaded files to Amazon S3 or similar
- Automatically add `lastModifiedBy` data to a document to track who changed what over time
- Encrypt a field's data when it's saved and decrypt it when it's read
- Send emails when `ContactSubmission`s are created from a public website
- Integrate with a payment provider like Stripe to automatically process payments when an `Order` is created
- Securely recalculate order prices on the backend to ensure that the total price for `Order`s that users submit is accurate and valid
- Generate and store a `lastLoggedIn` date on a user by adding an `afterLogin` hook
- Add extra data to documents before they are read such as "average scores" or similar data that needs to be calculated on the fly
There are many more use cases for Hooks and the sky is the limit.
#### Async vs. synchronous
All hooks can be written as either synchronous or asynchronous functions. If the Hook should modify data before a document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function, so you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
If your Hook simply performs a side-effect, such as updating a CRM, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
#### Server-only execution
Payload Hooks are only triggered on the server. You can safely [remove your hooks](/docs/admin/excluding-server-code#aliasing-server-only-modules) from your Admin panel's client-side code by customizing the Webpack config, which not only keeps your Admin bundles' filesize small but also ensures that any server-side only code does not cause problems within browser environments.
## Hook Types
You can specify hooks in the following contexts:
- [Collection Hooks](/docs/hooks/collections)
- [Field Hooks](/docs/hooks/fields)
- [Global Hooks](/docs/hooks/globals)

View File

@@ -1,103 +0,0 @@
---
title: Vercel Content Link
label: Vercel Content Link
order: 10
desc: Payload + Vercel Content Link allows yours editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it.
keywords: vercel, vercel content link, visual editing, content source maps, Content Management System, cms, headless, javascript, node, react, express
---
[Vercel Content Link](https://vercel.com/docs/workflow-collaboration/edit-mode#content-link) will allow your editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it. This requires no changes to your front-end code and very few changes to your Payload config.
![Versions](/images/docs/vercel-visual-editing.jpg)
<Banner type="warning">
Vercel Content Link is an enterprise-only feature and only available for deployments hosted on
Vercel. If you are an existing enterprise customer, [contact our sales
team](https://payloadcms.com/for-enterprise) for help with your integration.
</Banner>
### How it works
To power Vercel Content Link, Payload embeds Content Source Maps into its API responses. Content Source Maps are invisible, encoded JSON values that include a link back to the field in the CMS that generated the content. When rendered on the page, Vercel detects and decodes these values to display the Content Link interface.
For full details on how the encoding and decoding algorithm works, check out [`@vercel/stega`](https://www.npmjs.com/package/@vercel/stega).
### Getting Started
Setting up Payload with Vercel Content Link is easy. First, install the `@payloadcms/plugin-csm` plugin into your project. This plugin requires an API key to install, [contact our sales team](https://payloadcms.com/for-enterprise) if you don't already have one.
```bash
npm i @payloadcms/plugin-csm
```
Then in the `plugins` array of your Payload config, call the plugin and enable any collections that require Content Source Maps.
```ts
import { buildConfig } from "payload/config"
import contentSourceMaps from "@payloadcms/plugin-csm"
const config = buildConfig({
collections: [
{
slug: "pages",
fields: [
{
name: 'slug',
type: 'text',
},
{
name: 'title,'
type: 'text',
},
],
},
],
plugins: [
contentSourceMaps({
collections: ["pages"],
}),
],
})
export default config
```
Now in your Next.js app, include the `?encodeSourceMaps=true` parameter in any of your API requests. For performance reasons, this should only be done when in draft mode or on preview deployments.
```ts
if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
const res = await fetch(
`${process.env.NEXT_PUBLIC_PAYLOAD_CMS_URL}/api/pages?where[slug][equals]=${slug}&encodeSourceMaps=true`,
)
}
```
And that's it! You are now ready to enter Edit Mode and begin visually editing your content.
##### Edit Mode
To see Content Link on your site, you first need to visit any preview deployment on Vercel and login using the Vercel Toolbar. When Content Source Maps are detected on the page, a pencil icon will appear in the toolbar. Clicking this icon will enable Edit Mode, highlighting all editable fields on the page in blue.
![Versions](/images/docs/vercel-toolbar.jpg)
### Troubleshooting
##### Dates
The plugin does not encode `date` fields by default, but for some cases like text that uses negative CSS letter-spacing, it may be necessary to split the encoded data out from the rendered text. This way you can safely use the cleaned data as expected.
```ts
import { vercelStegaSplit } from '@vercel/stega'
const { cleaned, encoded } = vercelStegaSplit(text)
```
##### Blocks
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.
```ts
<div data-vercel-edit-target>
<span style={{ display: "none" }}>{encodedSourceMap}</span>
{children}
</div>
```

View File

@@ -1,284 +0,0 @@
---
title: Implementing Live Preview in your app
label: Frontend
order: 20
desc: Learn how to implement Live Preview in your front-end application.
keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, useLivePreview
---
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
By default, all hooks accept the following args:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`serverURL`** \* | The URL of your Payload server. |
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
| **`apiRoute`** | The path of your API route as defined in `routes.api`. Defaults to `/api`. |
_\* An asterisk denotes that a property is required._
And return the following values:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`data`** | The live data of the document, merged with the initial data. |
| **`isLoading`** | A boolean that indicates whether or not the document is loading. |
<Banner type="info">
If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
</Banner>
<Banner type="info">
If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
</Banner>
### React
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
First, install the `@payloadcms/live-preview-react` package:
```bash
npm install @payloadcms/live-preview-react
```
Then, use the `useLivePreview` hook in your React component:
```tsx
'use client';
import { useLivePreview } from '@payloadcms/live-preview-react';
import { Page as PageType } from '@/payload-types'
// Fetch the page in a server component, pass it to the client component, then thread it through the hook
// The hook will take over from there and keep the preview in sync with the changes you make
// The `data` property will contain the live data of the document
export const PageClient: React.FC<{
page: {
title: string
}
}> = ({ page: initialPage }) => {
const { data } = useLivePreview<PageType>({
initialData: initialPage,
serverURL: PAYLOAD_SERVER_URL,
depth: 2,
})
return (
<h1>{data.title}</h1>
)
}
```
### Vue
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
First, install the `@payloadcms/live-preview-vue` package:
```bash
npm install @payloadcms/live-preview-vue
```
Then, use the `useLivePreview` hook in your Vue component:
```vue
<script setup lang="ts">
import type { PageData } from '~/types';
import { defineProps } from 'vue';
import { useLivePreview } from '@payloadcms/live-preview-vue';
// Fetch the initial data on the parent component or using async state
const props = defineProps<{ initialData: PageData }>();
// The hook will take over from here and keep the preview in sync with the changes you make.
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
const { data } = useLivePreview<PageData>({
initialData: props.initialData,
serverURL: "<PAYLOAD_SERVER_URL>",
depth: 2,
});
</script>
<template>
<h1>{{ data.title }}</h1>
</template>
```
## Building your own hook
No matter what front-end framework you are using, you can build your own hook using the same underlying tooling that Payload provides.
First, install the base `@payloadcms/live-preview` package:
```bash
npm install @payloadcms/live-preview
```
This package provides the following functions:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`subscribe`** | Subscribes to the Admin panel's `window.postMessage` events and calls the provided callback function. |
| **`unsubscribe`** | Unsubscribes from the Admin panel's `window.postMessage` events. |
| **`ready`** | Sends a `window.postMessage` event to the Admin panel to indicate that the front-end is ready to receive messages. |
The `subscribe` function takes the following args:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
| **`serverURL`** \* | The URL of your Payload server. |
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
With these functions, you can build your own hook using your front-end framework of choice:
```tsx
import { subscribe, unsubscribe } from '@payloadcms/live-preview';
// To build your own hook, subscribe to Live Preview events using the`subscribe` function
// It handles everything from:
// 1. Listening to `window.postMessage` events
// 2. Merging initial data with active form state
// 3. Populating relationships and uploads
// 4. Calling the `onChange` callback with the result
// Your hook should also:
// 1. Tell the Admin panel when it is ready to receive messages
// 2. Handle the results of the `onChange` callback to update the UI
// 3. Unsubscribe from the `window.postMessage` events when it unmounts
```
Here is an example of what the same `useLivePreview` React hook from above looks like under the hood:
```tsx
import { subscribe, unsubscribe, ready } from '@payloadcms/live-preview'
import { useCallback, useEffect, useState, useRef } from 'react'
export const useLivePreview = <T extends any>(props: {
depth?: number
initialData: T
serverURL: string
}): {
data: T
isLoading: boolean
} => {
const { depth = 0, initialData, serverURL } = props
const [data, setData] = useState<T>(initialData)
const [isLoading, setIsLoading] = useState<boolean>(true)
const hasSentReadyMessage = useRef<boolean>(false)
const onChange = useCallback((mergedData) => {
// When a change is made, the `onChange` callback will be called with the merged data
// Set this merged data into state so that React will re-render the UI
setData(mergedData)
setIsLoading(false)
}, [])
useEffect(() => {
// Listen for `window.postMessage` events from the Admin panel
// When a change is made, the `onChange` callback will be called with the merged data
const subscription = subscribe({
callback: onChange,
depth,
initialData,
serverURL,
})
// Once subscribed, send a `ready` message back up to the Admin panel
// This will indicate that the front-end is ready to receive messages
if (!hasSentReadyMessage.current) {
hasSentReadyMessage.current = true
ready({
serverURL
})
}
// When the component unmounts, unsubscribe from the `window.postMessage` events
return () => {
unsubscribe(subscription)
}
}, [serverURL, onChange, depth, initialData])
return {
data,
isLoading,
}
}
```
<Banner type="info">
When building your own hook, ensure that the args and return values are consistent with the ones listed at the top of this document. This will ensure that all hooks follow the same API.
</Banner>
## Example
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). There you will find examples of various front-end frameworks and how to integrate each one of them, including:
- [Next.js App Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-app)
- [Next.js Pages Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-pages)
## Troubleshooting
#### Relationships and/or uploads are not populating
If you are using relationships or uploads in your front-end application, and your front-end application runs on a different domain than your Payload server, you may need to configure [CORS](../configuration/overview) to allow requests to be made between the two domains. This includes sites that are running on a different port or subdomain. Similarly, if you are protecting resources behind user authentication, you may also need to configure [CSRF](../authentication/overview#csrf-protection) to allow cookies to be sent between the two domains. For example:
```ts
// payload.config.ts
{
// ...
// If your site is running on a different domain than your Payload server,
// This will allows requests to be made between the two domains
cors: {
[
'http://localhost:3001' // Your front-end application
],
},
// If you are protecting resources behind user authentication,
// This will allow cookies to be sent between the two domains
csrf: {
[
'http://localhost:3001' // Your front-end application
],
},
}
```
#### Relationships and/or uploads disappear after editing a document
It is possible that either you are setting an improper [`depth`](../getting-started/concepts#depth) in your initial request and/or your `useLivePreview` hook, or they're mismatched. Ensure that the `depth` parameter is set to the correct value, and that it matches exactly in both places. For example:
```tsx
// Your initial request
const { docs } = await payload.find({
collection: 'pages',
depth: 1, // Ensure this is set to the proper depth for your application
where: {
slug: {
equals: 'home',
}
}
})
```
```tsx
// Your hook
const { data } = useLivePreview<PageType>({
initialData: initialPage,
serverURL: PAYLOAD_SERVER_URL,
depth: 1, // Ensure this matches the depth of your initial request
})
```
#### Iframe refuses to connect
If your front-end application has set a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) that blocks the Admin Panel from loading your front-end application, the iframe will not be able to load your site. To resolve this, you can whitelist the Admin Panel's domain in your CSP by setting the `frame-ancestors` directive:
```plaintext
frame-ancestors: "self" localhost:* https://your-site.com;
```

View File

@@ -1,157 +0,0 @@
---
title: Live Preview
label: Overview
order: 10
desc: With Live Preview you can render your front-end application directly within the Admin panel. Your changes take effect as you type. No save needed.
keywords: live preview, preview, live, iframe, iframe preview, visual editing, design
---
**With Live Preview you can render your front-end application directly within the Admin panel. As you type, your changes take effect in real-time. No need to save a draft or publish your changes.**
Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
{/* IMAGE OF LIVE PREVIEW HERE */}
<Banner type="warning">
Live Preview is currently in beta. You may use this feature in production, but please be aware that it is subject to change and may not be fully stable for all use cases. If you encounter any issues, please [report them](https://github.com/payloadcms/payload/issues/new?assignees=jacobsfletch&labels=possible-bug&projects=&title=Live%20Preview&template=1.bug_report.yml) with as much detail as possible.
</Banner>
## Setup
Setting up Live Preview is easy. You first need to enable it through the `admin.livePreview` property of your Payload config. It takes the following options:
| Path | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`url`** \* | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
| **`breakpoints`** | Array of breakpoints to be used as “device sizes” in the preview window. Each item appears as an option in the toolbar. [More details](#breakpoints). |
| **`collections`** | Array of collection slugs to enable Live Preview on. |
| **`globals`** | Array of global slugs to enable Live Preview on. |
_\* An asterisk denotes that a property is required._
Here is a basic example of enabling Live Preview on a `pages` collection:
```ts
// payload.config.ts
{
// ...
admin: {
// ...
livePreview: {
url: 'http://localhost:3000', // The URL to your front-end, this can also be a function (see below)
collections: ['pages'], // The collections to enable Live Preview on (globals are also possible)
},
}
}
```
Alternatively, you can define the <code>admin.livePreview</code> property on individual collection and global configs. Settings defined here will be merged into the top-level as overrides (if applicable).
```ts
// Collection.ts
{
// ...
admin: {
// ...
livePreview: {
url: 'http://localhost:3000',
},
}
}
```
Once configured, a new "Live Preview" tab will appear at the top of enabled documents. Navigating to this tab opens the preview window and loads your front-end application.
### URL
The `url` property is a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end.
You can also pass a function in order to dynamically format URLs. This function is called with the following arguments:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`data`** | The data of the document being edited. This includes changes that have not yet been saved. |
| **`documentInfo`** | Information about the document being edited like collection slug. [More details](../admin/hooks#usedocumentinfo). |
| **`locale`** | The locale currently being edited (if applicable). [More details](../configuration/localization). |
Here is an example of using a function that returns a dynamic URL:
```ts
// payload.config.ts
{
// ...
admin: {
// ...
livePreview: {
url: ({
data,
documentInfo,
locale
}) => `${data.tenant.url}${ // Multi-tenant top-level domain
documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `${data.slug !== 'home' : `/${data.slug}` : ''}`
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
collections: ['pages'],
},
}
}
```
### Breakpoints
The breakpoints property is an array of objects which are used as “device sizes” in the preview window. Each item will render as an option in the toolbar. When selected, the preview window will resize to the exact dimensions specified in that breakpoint. Each breakpoint takes the following properties:
| Path | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`label`** \* | The label to display in the drop-down. This is what the user will see. |
| **`name`** \* | The name of the breakpoint. |
| **`width`** \* | The width of the breakpoint. This is used to set the width of the iframe. |
| **`height`** \* | The height of the breakpoint. This is used to set the height of the iframe. |
_\* An asterisk denotes that a property is required._
Here is an example of defining breakpoints:
```ts
// payload.config.ts
{
// ...
admin: {
// ...
livePreview: {
url: 'http://localhost:3000',
breakpoints: [
{
label: 'Mobile',
name: 'mobile',
width: 375,
height: 667,
},
{
label: 'Tablet',
name: 'tablet',
width: 768,
height: 1024,
},
{
label: 'Desktop',
name: 'desktop',
width: 1440,
height: 900,
},
],
},
}
}
```
{/* IMAGE OF TOOLBAR HERE */}
The "Responsive" option is always available in the drop-down and requires no additional configuration. This is the default breakpoint that will be used on initial load. This option styles the iframe with a width and height of `100%` so that it fills the screen at its maximum size and automatically resizes as the window changes size.
You may also explicitly resize the Live Preview by using the corresponding inputs in the toolbar. This will temporarily override the breakpoint selection to "Custom" until a predefined breakpoint is selected once again.
If you prefer to freely resize the Live Preview without the use of breakpoints, you can open it in a new window by clicking the button in the toolbar. This will close the iframe and open a new window which can be resized as you wish. Closing it will automatically re-open the iframe.
## Example
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload).

View File

@@ -1,547 +0,0 @@
---
title: Local API
label: Overview
order: 10
desc: The Payload Local API allows you to interact with your database and execute the same operations that are available through REST and GraphQL within Node, directly on your server.
keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
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.
</Banner>
Here are some common examples of how you can use the Local API:
- Seeding data via Node seed scripts that you write and maintain
- Opening custom Express routes which feature additional functionality but still rely on Payload
- Within access control and hook functions
### Accessing payload
You can gain access to the currently running `payload` object via two ways:
##### Importing it
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 Express `req` - including within your access control and hook
functions.
Example:
```ts
const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } }) => {
const posts = await payload.find({
collection: 'posts',
})
}
```
### 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.
| 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._
<Banner type="warning">
<strong>Note:</strong>
<br />
By default, all access control checks are disabled in the Local API, but you can re-enable them if
you'd like, as well as pass a specific user to run the operation with.
</Banner>
## Collections
The following Collection operations are available through the Local API:
#### Create
```js
// The created Post document is returned
const post = await payload.create({
collection: 'posts', // required
data: {
// required
title: 'sure',
description: 'maybe',
},
locale: 'en',
fallbackLocale: false,
user: dummyUserDoc,
overrideAccess: true,
showHiddenFields: false,
// If creating verification-enabled auth doc,
// you can optionally disable the email that is auto-sent
disableVerificationEmail: true,
// If your collection supports uploads, you can upload
// a file directly through the Local API by providing
// its full, absolute file path.
filePath: path.resolve(__dirname, './path-to-image.jpg'),
// Alternatively, you can directly pass a File,
// if file is provided, filePath will be omitted
file: uploadedFile,
})
```
#### Find
```js
// Result will be a paginated set of Posts.
// See /docs/queries/pagination for more.
const result = await payload.find({
collection: 'posts', // required
depth: 2,
page: 1,
limit: 10,
pagination: false, // If you want to disable pagination count, etc.
where: {}, // pass a `where` query here
sort: '-title',
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Find by ID
```js
// Result will be a Post document.
const result = await payload.findByID({
collection: 'posts', // required
id: '507f1f77bcf86cd799439011', // required
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Count
```js
// Result will be an object with:
// {
// totalDocs: 10, // count of the documents satisfies query
// }
const result = await payload.count({
collection: 'posts', // required
locale: 'en',
where: {}, // pass a `where` query here
user: dummyUser,
overrideAccess: false,
})
```
#### Update by ID
```js
// Result will be the updated Post document.
const result = await payload.update({
collection: 'posts', // required
id: '507f1f77bcf86cd799439011', // required
data: {
// required
title: 'sure',
description: 'maybe',
},
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
// If your collection supports uploads, you can upload
// a file directly through the Local API by providing
// its full, absolute file path.
filePath: path.resolve(__dirname, './path-to-image.jpg'),
// If you are uploading a file and would like to replace
// the existing file instead of generating a new filename,
// you can set the following property to `true`
overwriteExistingFiles: true,
})
```
#### Update Many
```js
// Result will be an object with:
// {
// docs: [], // each document that was updated
// errors: [], // each error also includes the id of the document
// }
const result = await payload.update({
collection: 'posts', // required
where: {
// required
fieldName: { equals: 'value' },
},
data: {
// required
title: 'sure',
description: 'maybe',
},
depth: 0,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
// If your collection supports uploads, you can upload
// a file directly through the Local API by providing
// its full, absolute file path.
filePath: path.resolve(__dirname, './path-to-image.jpg'),
// If you are uploading a file and would like to replace
// the existing file instead of generating a new filename,
// you can set the following property to `true`
overwriteExistingFiles: true,
})
```
#### Delete
```js
// Result will be the now-deleted Post document.
const result = await payload.delete({
collection: 'posts', // required
id: '507f1f77bcf86cd799439011', // required
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Delete Many
```js
// Result will be an object with:
// {
// docs: [], // each document that is now deleted
// errors: [], // any errors that occurred, including the id of the errored on document
// }
const result = await payload.delete({
collection: 'posts', // required
where: {
// required
fieldName: { equals: 'value' },
},
depth: 0,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
## Auth Operations
If a collection has [`Authentication`](/docs/authentication/overview) enabled, additional Local API operations will be
available:
#### Login
```js
// result will be formatted as follows:
// {
// token: 'o38jf0q34jfij43f3f...', // JWT used for auth
// user: { ... } // the user document that just logged in
// exp: 1609619861 // the UNIX timestamp when the JWT will expire
// }
const result = await payload.login({
collection: 'users', // required
data: {
// required
email: 'dev@payloadcms.com',
password: 'rip',
},
req: req, // pass an Express `req` which will be provided to all hooks
res: res, // used to automatically set an HTTP-only auth cookie
depth: 2,
locale: 'en',
fallbackLocale: false,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Forgot Password
```js
// Returned token will allow for a password reset
const token = await payload.forgotPassword({
collection: 'users', // required
data: {
// required
email: 'dev@payloadcms.com',
},
req: req, // pass an Express `req` which will be provided to all hooks
})
```
#### Reset Password
```js
// Result will be formatted as follows:
// {
// token: 'o38jf0q34jfij43f3f...', // JWT used for auth
// user: { ... } // the user document that just logged in
// }
const result = await payload.resetPassword({
collection: 'users', // required
data: {
// required
password: req.body.password, // the new password to set
token: 'afh3o2jf2p3f...', // the token generated from the forgotPassword operation
},
req: req, // pass an Express `req` which will be provided to all hooks
res: res, // used to automatically set an HTTP-only auth cookie
})
```
#### Unlock
```js
// Returned result will be a boolean representing success or failure
const result = await payload.unlock({
collection: 'users', // required
data: {
// required
email: 'dev@payloadcms.com',
},
req: req, // pass an Express `req` which will be provided to all hooks
overrideAccess: true,
})
```
#### Verify
```js
// Returned result will be a boolean representing success or failure
const result = await payload.verifyEmail({
collection: 'users', // required
token: 'afh3o2jf2p3f...', // the token saved on the user as `_verificationToken`
})
```
## Globals
The following Global operations are available through the Local API:
#### Find
```js
// Result will be the Header Global.
const result = await payload.findGlobal({
slug: 'header', // required
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Update
```js
// Result will be the updated Header Global.
const result = await payload.updateGlobal({
slug: 'header', // required
data: {
// required
nav: [
{
url: 'https://google.com',
},
{
url: 'https://payloadcms.com',
},
],
},
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
## 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 initalized 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 initalized:
```
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).
Here is an example of usage:
```ts
// Properly inferred as `Post` type
const post = await payload.create({
collection: 'posts',
// Data will now be typed as Post and give you type hints
data: {
title: 'my title',
description: 'my description',
},
})
```

View File

@@ -1,294 +0,0 @@
---
title: Building Your Own Plugin
label: Build Your Own
order: 50
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Building your own plugin is easy, and if you&apos;re already familiar with Payload then you&apos;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.
<Banner type="success">
To use the template, run `npx create-payload-app@latest -t plugin -n my-new-plugin` directly in your terminal or [clone the template directly from GitHub](https://github.com/payloadcms/payload-plugin-template).
</Banner>
Our plugin template includes everything you need to build a full life-cycle plugin:
* Example files and functions for extending the payload config
* A local dev environment to develop the plugin
* Test suite with integrated GitHub workflow
By abstracting your code into a plugin, you&apos;ll be able to reuse your feature across multiple projects and make it available for other developers to use.
### Plugins Recap
Here is a brief recap of how to integrate plugins with Payload, to learn more head back to the [plugin overview page](https://payloadcms.com/docs/plugins/overview).
#### How to install a plugin
To install any plugin, simply add it to your Payload config in the plugins array.
```
import samplePlugin from 'sample-plugin';
const config = buildConfig({
plugins: [
// Add plugins here
samplePlugin({
enabled: true,
}),
],
});
export default config;
```
#### Initialization
The initialization process goes in the following order:
1. Incoming config is validated
2. Plugins execute
3. Default options are integrated
4. Sanitization cleans and validates data
5. Final config gets initialized
### 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:
1. root folder - general configuration
2. /src folder - everything related to the plugin
3. /dev folder - sanitized test project for development
#### Root
In the root folder, you will see various files related to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects. The only two files you need to modify are:
* **README**.md - This contains instructions on how to use the template. When you are ready, update this to contain instructions on how to use your Plugin.
* **package**.json - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
#### Dev
The purpose of the **dev** folder is to provide a sanitized local Payload project. so you can run and test your plugin while you are actively developing it.
Do **not** store any of the plugin functionality in this folder - it is purely an environment to _assist_ you with developing the plugin.
If you&apos;re starting from scratch, you can easily setup a dev environment like this:
```
mkdir dev
cd dev
npx create-payload-app@latest
```
If you&apos;re using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config()`.
```
plugins: [
// when you rename the plugin or add options, make sure to update it here
samplePlugin({
enabled: false,
})
]
```
You can add to the `dev/payload.config` and build out the dev project as needed to test your plugin.
When you&apos;re ready to start development, navigate into this folder with `cd dev`
And then start the project with `yarn dev` and pull up `http://localhost:3000` in your browser.
### Testing
Another benefit of the dev folder is that you have the perfect environment established for testing.
A good test suite is essential to ensure quality and stability in your plugin. Payload typically uses [Jest](https://jestjs.io/); a popular testing framework, widely used for testing JavaScript and particularly for applications built with React.
Jest organizes tests into test suites and cases. We recommend creating tests based on the expected behavior of your plugin from start to finish. Read more about tests in the [Jest documentation.](https://jestjs.io/)
The plugin template provides a stubbed out test suite at `dev/plugin.spec.ts` which is ready to go - just add in your own test conditions and you&apos;re all set!
```
import payload from 'payload'
describe('Plugin tests', () => {
// Example test to check for seeded data
it('seeds data accordingly', async () => {
const newCollectionQuery = await payload.find({
collection: 'newCollection',
sort: 'createdAt',
})
newCollection = newCollectionQuery.docs
expect(newCollectionQuery.totalDocs).toEqual(1)
})
})
```
### Seeding data
For development and testing, you will likely need some data to work with. You can streamline this process by seeding and dropping your database - instead of manually entering data.
In the plugin template, you can navigate to `dev/src/server.ts` and see an example seed function.
```
if (process.env.PAYLOAD_SEED === 'true') {
await seed(payload)
}
```
A sample seed function has been created for you at `dev/src/seed`, update this file with additional data as needed.
```
export const seed = async (payload: Payload): Promise<void> => {
payload.logger.info('Seeding data...')
await payload.create({
collection: 'new-collection',
data: {
title: 'Seeded title',
},
})
// Add additional seed data here
}
```
#### Src
Now that we have our environment setup and dev project ready to go - it&apos;s time to build the plugin!
**index.ts**
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
**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.
```
export const samplePlugin =
(pluginOptions: PluginTypes) =>
(incomingConfig: Config): Config => {
let config = { ...incomingConfig }
// do something cool with the config here
return config
}
```
1. First, you need to receive the existing Payload config along with any plugin options.
2. Then set the variable `config` to be equal to a copy of the existing config.
3. From here, you can extend the config however you like!
4. Finally, return the config and you&apos;re all set.
### Spread Syntax
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly, else this can cause adverse behavior and conflicts with Payload config and other plugins.
Let&apos;s say you want to build a plugin that adds a new collection:
```
config.collections = [
...(config.collections || []),
newCollection,
// Add additional collections here
]
```
First, you need to spread the `config.collections` to ensure that we don&apos;t lose the existing collections. Then you can add any additional collections, just as you would in a regular payload config.
This same logic is applied to other properties like admin, globals, hooks:
```
config.globals = [
...(config.globals || []),
// Add additional globals here
]
config.hooks = {
...(config.hooks || {}),
// Add additional hooks here
}
```
Some properties will be slightly different to extend, for instance the `onInit` property:
```
config.onInit = async payload => {
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
// Add additional onInit code by using the onInitExtension function
onInitExtension(pluginOptions, payload)
}
```
If you wish to add to the `onInit`, you must include the async/await. We don&apos;t use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
### Webpack
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.
Webpack is another part of the Payload config that can be a little more tricky to extend. To help here, the template includes a helper function `extendWebpackConfig()` which takes care of spreading the existing webpack, so you can just add your new stuff:
```
config.admin = {
...(config.admin || {}),
// Add your aliases to the helper function below
webpack: extendWebpackConfig(incomingConfig)
}
```
### Types
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.
```
export interface PluginTypes {
/**
* Enable or disable plugin
* @default false
*/
enabled?: boolean
}
```
If possible, include [JSDoc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#types-1) to describe the options and their types. This allows a developer to see details about the options in their editor.
### Best practices
In addition to the setup covered above, here are other best practices to follow:
##### Providing an enable / disable option:
For a better user experience, provide a way to disable the plugin without uninstalling it. This is especially important if your plugin adds additional webpack aliases, this will allow you to still let the webpack run to prevent errors.
##### Include tests in your GitHub CI workflow:
If you&apos;ve configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs)
##### Publish your finished plugin to NPM:
The best way to share and allow others to use your plugin once it is complete is to publish an NPM package. This process is straightforward and well documented, find out more about [creating and publishing a NPM package here](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/).
##### Add payload-plugin topic tag:
Apply the tag **payload-plugin** to your GitHub repository. This will boost the visibility of your plugin and ensure it gets listed with [existing payload plugins](https://github.com/topics/payload-plugin).
##### Use [Semantic Versioning](https://semver.org/) (SemVer):
With the SemVer system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.

View File

@@ -1,400 +0,0 @@
---
title: Form Builder Plugin
label: Form Builder
order: 20
desc: Easily build and manage forms from the admin panel. Send dynamic, personalized emails and even accept and process payments.
keywords: plugins, plugin, form, forms, form builder
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-form-builder)](https://www.npmjs.com/package/@payloadcms/plugin-form-builder)
This plugin allows you to build and manage custom forms directly within the admin panel. Instead of hard-coding a new form into your website or application every time you need one, admins can simply define the schema for each form they need on-the-fly, and your front-end can map over this schema, render its own UI components, and match your brand's design system.
All form submissions are stored directly in your database and are managed directly from the admin panel. When forms are submitted, you can display a custom on-screen confirmation message to the user or redirect them to a dedicated confirmation page. You can even send dynamic, personalized emails derived from the form's data. For example, you may want to send a confirmation email to the user who submitted the form, and also send a notification email to your team.
Forms can be as simple or complex as you need, from a basic contact form, to a multi-step lead generation engine, or even a donation form that processes payment. You may not need to reach for third-party services like HubSpot or Mailchimp for this, but instead use your own first-party tooling, built directly into your own application.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-form-builder). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20form-builder&template=bug_report.md&title=plugin-form-builder%3A) with as much detail as possible.
</Banner>
##### Core Features
- Build completely dynamic forms directly from the admin panel for a variety of use cases
- Render forms on your front-end using your own UI components and match your brand's design system
- Send dynamic, personalized emails upon form submission to multiple recipients, derived from the form's data
- Display a custom confirmation message or automatically redirect upon form submission
- Build dynamic prices based on form input to use for payment processing (optional)
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-form-builder
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload/config'
import formBuilder from '@payloadcms/plugin-form-builder'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
],
plugins: [
formBuilder({
// see below for a list of available options
})
],
})
export default config
```
### Options
#### `fields` (option)
The `fields` property is an object of field types to allow your admin editors to build forms with. To override default settings, pass either a boolean value or a partial [Payload Block](https://payloadcms.com/docs/fields/blocks#block-configs) _keyed to the block's slug_. See [Fields](#fields) for more details.
```ts
// payload.config.ts
formBuilder({
// ...
fields: {
text: true,
textarea: true,
select: true,
email: true,
state: true,
country: true,
checkbox: true,
number: true,
message: true,
payment: false
}
})
```
#### `redirectRelationships`
The `redirectRelationships` property is an array of collection slugs that, when enabled, are populated as options in the form's `redirect` field. This field is used to redirect the user to a dedicated confirmation page upon form submission (optional).
```ts
// payload.config.ts
formBuilder({
// ...
redirectRelationships: ['pages']
})
```
#### `beforeEmail`
The `beforeEmail` property is a [beforeChange](https://payloadcms.com/docs/hooks/globals#beforechange) hook that is called just after emails are prepared, but before they are sent. This is a great place to inject your own HTML template to add custom styles.
```ts
// payload.config.ts
formBuilder({
// ...
beforeEmail: (emailsToSend) => {
// modify the emails in any way before they are sent
return emails.map((email) => ({
...email,
html: email.html, // transform the html in any way you'd like (maybe wrap it in an html template?)
}))
}
})
```
#### `formOverrides`
Override anything on the `forms` collection by sending a [Payload Collection Config](https://payloadcms.com/docs/configuration/collections) to the `formOverrides` property.
```ts
// payload.config.ts
formBuilder({
// ...
formOverrides: {
slug: "contact-forms",
access: {
read: () => true,
update: () => false,
},
fields: [
{
name: "custom-field",
type: "text"
}
]
}
})
```
#### `formSubmissionOverrides`
Override anything on the `form-submissions` collection by sending a [Payload Collection Config](https://payloadcms.com/docs/configuration/collections) to the `formSubmissionOverrides` property.
<Banner type="warning">
By default, this plugin relies on [Payload access control](https://payloadcms.com/docs/access-control/collections) to restrict the `update` and `read` operations on the `form-submissions` collection. This is because _anyone_ should be able to create a form submission, even from a public-facing website, but _no one_ should be able to update a submission one it has been created, or read a submission unless they have permission. You can override this behavior or any other property as needed.
</Banner>
```ts
// payload.config.ts
formBuilder({
// ...
formSubmissionOverrides: {
slug: "leads",
}
})
```
#### `handlePayment`
The `handlePayment` property is a [beforeChange](<[beforeChange](https://payloadcms.com/docs/hooks/globals#beforechange)>) hook that is called upon form submission. You can integrate into any third-party payment processing API here to accept payment based on form input. You can use the `getPaymentTotal` function to calculate the total cost after all conditions have been applied. This is only applicable if the form has enabled the `payment` field.
First import the utility function. This will execute all of the price conditions that you have set in your form's `payment` field and returns the total price.
```ts
// payload.config.ts
import { getPaymentTotal } from '@payloadcms/plugin-form-builder';
```
Then in your plugin's config:
```ts
// payload.config.ts
formBuilder({
// ...
handlePayment: async ({ form, submissionData }) => {
// first calculate the price
const paymentField = form.fields?.find((field) => field.blockType === 'payment');
const price = getPaymentTotal({
basePrice: paymentField.basePrice,
priceConditions: paymentField.priceConditions,
fieldValues: submissionData,
});
// then asynchronously process the payment here
}
})
```
## Fields
Each field represents a form input. To override default settings pass either a boolean value or a partial [Payload Block](https://payloadcms.com/docs/fields/blocks) _keyed to the block's slug_. See [Field Overrides](#field-overrides) for more details on how to do this.
<Banner type="info">
<strong>Note:</strong>
"Fields" here is in reference to the _fields to build forms with_, not to be confused with the _fields of a collection_ which are set via `formOverrides.fields`.
</Banner>
#### Text
Maps to a `text` input in your front-end. Used to collect a simple string.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### Textarea
Maps to a `textarea` input on your front-end. Used to collect a multi-line string.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### Select
Maps to a `select` input on your front-end. Used to display a list of options.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
| `options` | array | An array of objects with `label` and `value` properties. |
#### Email (field)
Maps to a `text` input with type `email` on your front-end. Used to collect an email address.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### State
Maps to a `select` input on your front-end. Used to collect a US state.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### Country
Maps to a `select` input on your front-end. Used to collect a country.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### Checkbox
Maps to a `checkbox` input on your front-end. Used to collect a boolean value.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | checkbox | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
#### Number
Maps to a `number` input on your front-end. Used to collect a number.
| Property | Type | Description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. || `defaultValue` | number | The default value of the field. |
#### Message
Maps to a `RichText` component on your front-end. Used to display an arbitrary message to the user anywhere in the form.
| property | type | description |
| --- | --- | --- |
| `message` | richText | The message to display on the form. |
#### Payment
Add this field to your form if it should collect payment. Upon submission, the `handlePayment` callback is executed with the form and submission data. You can use this to integrate with any third-party payment processing API.
| property | type | description |
| --- | --- | --- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | number | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
| `priceConditions` | array | An array of objects that define the price conditions. See below for more details. |
##### Price Conditions
Each of the `priceConditions` are executed by the `getPaymentTotal` utility that this plugin provides. You can call this function in your `handlePayment` callback to dynamically calculate the total price of a form upon submission based on the user's input. For example, you could create a price condition that says "if the user selects 'yes' for this checkbox, add $10 to the total price".
| property | type | description |
| --- | --- | --- |
| `fieldToUse` | relationship | The field to use to determine the price. |
| `condition` | string | The condition to use to determine the price. |
| `valueForOperator` | string | The value to use for the operator. |
| `operator` | string | The operator to use to determine the price. |
| `valueType` | string | The type of value to use to determine the price. |
| `value` | string | The value to use to determine the price. |
#### Field Overrides
You can provide your own custom fields by passing a new [Payload Block](https://payloadcms.com/docs/fields/blocks#block-configs) object into `fields`. You can override or extend any existing fields by first importing the `fields` from the plugin:
```ts
import { fields } from '@payloadcms/plugin-form-builder'
```
Then merging it into your own custom field:
```ts
// payload.config.ts
formBuilder({
// ...
fields: {
text: {
...fields.text,
labels: {
singular: "Custom Text Field",
plural: "Custom Text Fields",
}
}
}
})
```
## Email
This plugin relies on the [email configuration](https://payloadcms.com/docs/email/overview) defined in your `payload.init()`. It will read from your config and attempt to send your emails using the credentials provided.
## TypeScript
All types can be directly imported:
```js
import {
PluginConfig,
Form,
FormSubmission,
FieldsConfig,
BeforePayment,
BeforeEmail,
HandlePayment,
...
} from "@payloadcms/plugin-form-builder/types";
```
## Examples
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an official [Form Builder Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/form-builder) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. We've also included an in-depth walk-through of how to build a form from scratch in our [Form Builder Plugin Blog Post](https://payloadcms.com/blog/create-custom-forms-with-the-official-form-builder-plugin).
## Troubleshooting
Below are some common troubleshooting tips. To help other developers, please contribute to this section as you troubleshoot your own application.
##### SendGrid 403 Forbidden Error
- If you are using [SendGrid Link Branding](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-link-branding) to remove the "via sendgrid.net" part of your email, you must also setup [Domain Authentication](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication). This means you can only send emails from an address on this domain — so the `from` addresses in your form submission emails **_cannot_** be anything other than `something@your_domain.com`. This means that from `{{email}}` will not work, but `website@your_domain.com` will. You can still send the form's email address in the body of the email.
## Screenshots
![screenshot 1](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-1.jpg?raw=true)
<br />
![screenshot 2](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-2.jpg?raw=true)
<br />
![screenshot 3](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-3.jpg?raw=true)
<br />
![screenshot 4](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-4.jpg?raw=true)
<br />
![screenshot 5](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-5.jpg?raw=true)
<br />
![screenshot 6](https://github.com/payloadcms/plugin-form-builder/blob/main/images/screenshot-6.jpg?raw=true)

View File

@@ -1,239 +0,0 @@
---
title: Nested Docs Plugin
label: Nested Docs
order: 20
desc: Nested documents in a parent, child, and sibling relationship.
keywords: plugins, nested, documents, parent, child, sibling, relationship
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-nested-docs)](https://www.npmjs.com/package/@payloadcms/plugin-nested-docs)
This plugin allows you to easily nest the documents of your application inside of one another. It does so by adding a
new `parent` field onto each of your documents that, when selected, attaches itself to the parent's tree. When you edit
the great-great-grandparent of a document, for instance, all of its descendants are recursively updated. This is an
extremely powerful way of achieving hierarchy within a collection, such as parent/child relationship between pages.
Documents also receive a new `breadcrumbs` field. Once a parent is assigned, these breadcrumbs are populated based on
each ancestor up the tree. Breadcrumbs allow you to dynamically generate labels and URLs based on the document's
position in the hierarchy. Even if the slug of a parent document changes, or the entire tree is nested another level
deep, changes will cascade down the entire tree and all breadcrumbs will reflect those changes.
With this pattern you can perform whatever side-effects your applications needs on even the most deeply nested
documents. For example, you could easily add a custom `fullTitle` field onto each document and inject the parent's title
onto it, such as "Parent Title > Child Title". This would allow you to then perform searches and filters based on _that_
field instead of the original title. This is especially useful if you happen to have two documents with identical titles
but different parents.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-nested-docs). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20nested-docs&template=bug_report.md&title=plugin-nested-docs%3A) with as much detail as possible.
</Banner>
##### Core features
- Automatically adds a `parent` relationship field to each document
- Allows for parent/child relationships between documents within the same collection
- Recursively updates all descendants when a parent is changed
- Automatically populates a `breadcrumbs` field with all ancestors up the tree
- Dynamically generate labels and URLs for each breadcrumb
- Supports localization
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com),
or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-nested-docs
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin
with [options](#options):
```ts
import { buildConfig } from 'payload/config'
import nestedDocs from '@payloadcms/plugin-nested-docs'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'slug',
type: 'text',
},
],
},
],
plugins: [
nestedDocs({
collections: ['pages'],
generateLabel: (_, doc) => doc.title,
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
}),
],
})
export default config
```
### Fields
#### Parent
The `parent` relationship field is automatically added to every document which allows editors to choose another document
from the same collection to act as the direct parent.
#### Breadcrumbs
The `breadcrumbs` field is an array which dynamically populates all parent relationships of a document up to the top
level and stores the following fields.
| Field | Description |
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `label` | The label of the breadcrumb. This field is automatically set to either the `collection.admin.useAsTitle` (if defined) or is set to the `ID` of the document. You can also dynamically define the `label` by passing a function to the options property of [`generateLabel`](#generateLabel). |
| `url` | The URL of the breadcrumb. By default, this field is undefined. You can manually define this field by passing a property called function to the plugin options property of [`generateURL`](#generateURL). |
### Options
#### `collections`
An array of collections slugs to enable nested docs.
#### `generateLabel`
Each `breadcrumb` has a required `label` field. By default, its value will be set to the collection's `admin.useAsTitle`
or fallback the the `ID` of the document.
You can also pass a function to dynamically set the `label` of your breadcrumb.
```ts
// payload.config.ts
nestedDocs({
//...
generateLabel: (_, doc) => doc.title // NOTE: 'title' is a hypothetical field
})
```
The function takes two arguments and returns a string:
| Argument | Type | Description |
|----------|----------|----------------------------------------------|
| `docs` | `Array` | An array of the breadcrumbs up to that point |
| `doc` | `Object` | The current document being edited |
#### `generateURL`
A function that allows you to dynamically generate each breadcrumb `url`. Each `breadcrumb` has an optional `url` field
which is undefined by default. For example, you might want to format a full URL to contain all breadcrumbs up to
that point, like `/about-us/company/our-team`.
```ts
// payload.config.ts
nestedDocs({
//...
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''), // NOTE: 'slug' is a hypothetical field
})
```
| Argument | Type | Description |
|----------|----------|----------------------------------------------|
| `docs` | `Array` | An array of the breadcrumbs up to that point |
| `doc` | `Object` | The current document being edited |
#### `parentFieldSlug`
When defined, the `parent` field will not be provided for you automatically, and instead, expects you to add your
own `parent` field to each collection manually. This gives you complete control over where you put the field in your
admin dashboard, etc. Set this property to the `name` of your custom field.
#### `breadcrumbsFieldSlug`
When defined, the `breadcrumbs` field will not be provided for you, and instead, expects you to add your
own `breadcrumbs` field to each collection manually. Set this property to the `name` of your custom field.
<Banner type="info">
<strong>Note:</strong>
<br />
If you opt out of automatically being provided a `parent` or `breadcrumbs` field, you need to make sure that both fields are placed at the top-level of your document. They cannot exist within any nested data structures like a `group`, `array`, or `blocks`.
</Banner>
## Overrides
You can also extend the built-in `parent` and `breadcrumbs` fields per collection by using the `createParentField`
and `createBreadcrumbField` methods. They will merge your customizations overtop the plugin's base field configurations.
```ts
import { CollectionConfig } from "payload/types";
import { createParentField } from "@payloadcms/plugin-nested-docs/fields";
import { createBreadcrumbsField } from "@payloadcms/plugin-nested-docs/fields";
const examplePageConfig: CollectionConfig = {
slug: "pages",
fields: [
createParentField(
// First argument is equal to the slug of the collection
// that the field references
"pages",
// Second argument is equal to field overrides that you specify,
// which will be merged into the base parent field config
{
admin: {
position: "sidebar",
},
// Note: if you override the `filterOptions` of the `parent` field,
// be sure to continue to prevent the document from referencing itself as the parent like this:
// filterOptions: ({ id }) => ({ id: {not_equals: id }})`
}
),
createBreadcrumbsField(
// First argument is equal to the slug of the collection
// that the field references
"pages",
// Argument equal to field overrides that you specify,
// which will be merged into the base `breadcrumbs` field config
{
label: "Page Breadcrumbs",
}
),
],
};
```
<Banner type="warning">
<strong>Note:</strong>
<br />
If overriding the `name` of either `breadcrumbs` or `parent` fields, you must specify the `breadcrumbsFieldSlug` or `parentFieldSlug` respectively.
</Banner>
## Localization
This plugin supports localization by default. If the `localization` property is set in your Payload config,
the `breadcrumbs` field is automatically localized. For more details on how localization works in Payload, see
the [Localization](https://payloadcms.com/docs/configuration/localization) docs.
## TypeScript
All types can be directly imported:
```ts
import { PluginConfig, GenerateURL, GenerateLabel } from '@payloadcms/plugin-nested-docs/types'
```
## Examples
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an
official [Nested Docs Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs) which
demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an
official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website)
and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce), both of which use this
plugin.

View File

@@ -1,153 +0,0 @@
---
title: Plugins
label: Overview
order: 10
desc: Plugins provide a great way to modularize Payload functionalities into easy-to-use enhancements and extensions of your Payload apps.
keywords: plugins, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload comes with a built-in Plugins infrastructure that allows developers to build their own modular and easily reusable sets of functionality.
<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
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
- Add password-protection functionality to certain documents
- Add a full e-commerce backend to any Payload app
- Add custom reporting views to Payload's admin panel
- Encrypt specific collections' data
- Add a full form builder implementation
- 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
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).
```js
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'
import { viteBundler } from '@payloadcms/bundler-vite'
import { webpackBundler } from '@payloadcms/bundler-webpack'
const config = buildConfig({
bundler: webpackBundler() // or viteBundler(),
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
required: true,
},
],
},
],
db: mongooseAdapter({}) // or postgresAdapter({})
plugins: [
// Many plugins require options to be passed.
// In the following example, we call the function
// and pass it options accordingly
passwordProtect(['pages']),
// This plugin takes no options and just
// needs to be passed directly
addLastModified,
// ..
// To understand how to use the plugins you're interested in,
// consult their corresponding documentation
],
})
export default config
```
#### When Plugins are initialized
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.
```ts
import { Config, Plugin } from 'payload/config'
const addLastModified: Plugin = (incomingConfig: Config): Config => {
// Find all incoming auth-enabled collections
// so we can create a lastModifiedBy relationship field
// to all auth collections
const authEnabledCollections = incomingConfig.collections.filter((collection) =>
Boolean(collection.auth),
)
// Spread the existing config
const config: Config = {
...incomingConfig,
collections: incomingConfig.collections.map((collection) => {
// Spread each item that we are modifying,
// and add our new field - complete with
// hooks and proper admin UI config
return {
...collection,
fields: [
...collection.fields,
{
name: 'lastModifiedBy',
type: 'relationship',
relationTo: authEnabledCollections.map(({ slug }) => slug),
hooks: {
beforeChange: [
({ req }) => ({
value: req?.user?.id,
relationTo: req?.user?.collection,
}),
],
},
admin: {
position: 'sidebar',
readOnly: true,
},
},
],
}
}),
}
return config
}
export default addLastModified
```
### Available Plugins
Payload supports both official plugins, maintained by the Payload team, and community plugins, developed by external contributors.
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin). These plugins offer a wide range of functionality. Some are maintained by the Payload team, while others are community-built. While we encourage users to explore them, please note that only official plugins are maintained and supported by the Payload team. For community plugins, support may vary as they are developed and maintained independently.
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.
For a list of official plugins, check the [Payload monorepo](https://github.com/payloadcms/payload/tree/main/packages).

View File

@@ -1,75 +0,0 @@
---
title: Redirects Plugin
label: Redirects
order: 20
desc: Automatically create redirects for your Payload application
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-redirects)](https://www.npmjs.com/package/@payloadcms/plugin-redirects)
This plugin allows you to easily manage redirects for your application from within your admin panel. It does so by adding a `redirects` collection to your config that allows you specify a redirect from one URL to another. Your front-end application can use this data to automatically redirect users to the correct page using proper HTTP status codes. This is useful for SEO, indexing, and search engine ranking when re-platforming or when changing your URL structure.
For example, if you have a page at `/about` and you want to change it to `/about-us`, you can create a redirect from the old page to the new one, then you can use this data to write HTTP redirects into your front-end application. This will ensure that users are redirected to the correct page without penalty because search engines are notified of the change at the request level. This is a very lightweight plugin that will allow you to integrate managed redirects for any front-end framework.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-redirects). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%25redirects&template=bug_report.md&title=plugin-redirects%3A) with as much detail as possible.
</Banner>
##### Core features
- Adds a `redirects` collection to your config that:
- includes a `from` and `to` fields
- allows `to` to be a document reference
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-redirects
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from "payload/config";
import redirects from "@payloadcms/plugin-redirects";
const config = buildConfig({
collections: [
{
slug: "pages",
fields: [],
},
],
plugins: [
redirects({
collections: ["pages"],
}),
],
});
export default config;
```
### Options
| Option | Type | Description |
| --- | --- | --- |
| `collections` | `string[]` | An array of collection slugs to populate in the `to` field of each redirect. |
| `overrides` | `object` | A partial collection config that allows you to override anything on the `redirects` collection. |
## TypeScript
All types can be directly imported:
```ts
import { PluginConfig } from "@payloadcms/plugin-redirects/types";
```
## Examples
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an official [Redirects Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/redirects) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommere), both of which use this plugin.

View File

@@ -1,146 +0,0 @@
---
title: Search Plugin
label: Search
order: 20
desc: Generates records of your documents that are extremely fast to search on.
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-search)](https://www.npmjs.com/package/@payloadcms/plugin-search)
This plugin generates records of your documents that are extremely fast to search on. It does so by creating a new `search` collection that is indexed in the database then saving a static copy of each of your documents using only search-critical data. Search records are automatically created, synced, and deleted behind-the-scenes as you manage your application's documents.
For example, if you have a posts collection that is extremely large and complex, this would allow you to sync just the title, excerpt, and slug of each post so you can query on _that_ instead of the original post directly. Search records are static, so querying them also has the significant advantage of bypassing any hooks that may present be on the original documents. You define exactly what data is synced, and you can even modify or fallback this data before it is saved on a per-document basis.
To query search results, use all the existing Payload APIs that you are already familiar with. You can also prioritize search results by setting a custom priority for each collection. For example, you may want to list blog posts before pages. Or you may want one specific post to always take appear first. Search records are given a `priority` field that can be used as the `?sort=` parameter in your queries.
This plugin is a great way to implement a fast, immersive search experience such as a search bar in a front-end application. Many applications may not need the power and complexity of a third-party service like Algolia or ElasticSearch. This plugin provides a first-party alternative that is easy to set up and runs entirely on your own database.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-search). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20search&template=bug_report.md&title=plugin-search%3A) with as much detail as possible.
</Banner>
##### Core Features
- Automatically adds an indexed `search` collection to your database
- Automatically creates, syncs, and deletes search records as you manage your documents
- Saves only search-critical data that you define (e.g. title, excerpt, etc.)
- Allows you to query search results using first-party Payload APIs
- Allows you to query documents without triggering any of their underlying hooks
- Allows you to easily prioritize search results by collection or document
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-search
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```js
import { buildConfig } from 'payload/config'
import search from '@payloadcms/plugin-search'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
{
slug: 'posts',
fields: [],
},
],
plugins: [
search({
collections: ['pages', 'posts'],
defaultPriorities: {
pages: 10,
posts: 20,
},
}),
],
})
export default config
```
### Options
#### `collections`
The `collections` property is an array of collection slugs to enable syncing to search. Enabled collections receive a `beforeChange` and `afterDelete` hook that creates, updates, and deletes its respective search record as it changes over time.
#### `defaultPriorities`
This plugin automatically adds a `priority` field to the `search` collection that can be used as the `?sort=` parameter in your queries. For example, you may want to list blog posts before pages. Or you may want one specific post to always take appear first.
The `defaultPriorities` property is used to set a fallback `priority` on search records during the `create` operation. It accepts an object with keys that are your collection slugs and values that can either be a number or a function that returns a number. The function receives the `doc` as an argument, which is the document being created.
```ts
// payload.config.ts
{
// ...
searchPlugin({
defaultPriorities: {
pages: ({ doc }) => (doc.title.startsWith('Hello, world!') ? 1 : 10),
posts: 20,
},
}),
}
```
#### `searchOverrides`
This plugin automatically creates the `search` collection, but you can override anything on this collection via the `searchOverrides` property. It accepts anything from the [Payload Collection Config](https://payloadcms.com/docs/configuration/collections) and merges it in with the default `search` collection config provided by the plugin.
```ts
// payload.config.ts
{
// ...
searchPlugin({
searchOverrides: {
slug: 'search-results',
},
}),
}
```
#### `beforeSync`
Before creating or updating a search record, the `beforeSync` function runs. This is an [afterChange](https://payloadcms.com/docs/hooks/globals#afterchange) hook that allows you to modify the data or provide fallbacks before its search record is created or updated.
```ts
// payload.config.ts
{
// ...
searchPlugin({
beforeSync: ({ originalDoc, searchDoc }) => ({
...searchDoc,
// Modify your docs in any way here, this can be async
excerpt: originalDoc?.excerpt || 'This is a fallback excerpt',
}),
}),
}
```
#### `syncDrafts`
When `syncDrafts` is true, draft documents will be synced to search. This is false by default. You must have [Payload Drafts](https://payloadcms.com/docs/versions/drafts) enabled for this to apply.
#### `deleteDrafts`
If true, will delete documents from search whose status changes to draft. This is true by default. You must have [Payload Drafts](https://payloadcms.com/docs/versions/drafts) enabled for this to apply.
## TypeScript
All types can be directly imported:
```ts
import type { SearchConfig, BeforeSync } from '@payloadcms/plugin-search/types'
```

View File

@@ -1,133 +0,0 @@
---
title: Sentry Plugin
label: Sentry
order: 20
desc: Integrate Sentry error tracking into your Payload application
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-sentry)](https://www.npmjs.com/package/@payloadcms/plugin-sentry)
This plugin allows you to integrate [Sentry](https://sentry.io/) seamlessly with your [Payload](https://github.com/payloadcms/payload) application.
### What is Sentry?
Sentry is a powerful error tracking and performance monitoring tool that helps developers identify, diagnose, and resolve issues in their applications.
<Banner type="success">
Sentry does smart stuff with error data to make bugs easier to find and fix. - [sentry.io](https://sentry.io/)
</Banner>
This multi-faceted software offers a range of features that will help you manage errors with greater ease and ultimately ensure your application is running smoothly:
#### Core Features
- **Error Tracking**: Instantly captures and logs errors as they occur in your application
- **Performance Monitoring**: Tracks application performance to identify slowdowns and bottlenecks
- **Detailed Reports**: Provides comprehensive insights into errors, including stack traces and context
- **Alerts and Notifications**: Send and customize event-triggered notifications
- **Issue Grouping, Filtering and Search**: Automatically groups similar errors, and allows filtering and searching issues by custom criteria
- **Breadcrumbs**: Records user actions and events leading up to an error
- **Integrations**: Connects with various tools and services for enhanced workflow and issue management
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-sentry). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-seo%3A) with as much detail as possible.
</Banner>
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-sentry
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin and pass in your Sentry DSN as an option.
```ts
import { buildConfig } from 'payload/config'
import { sentry } from '@payloadcms/plugin-sentry'
import { Pages, Media } from './collections'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
sentry({
dsn: 'https://61edebas776889984d323d777@o4505289711681536.ingest.sentry.io/4505357433352176',
}),
],
})
export default config
```
## Options
- `dsn` : string | **required**
Sentry automatically assigns a DSN when you create a project, the unique DSN informs Sentry where to send events so they are associated with the correct project.
<Banner type="warning">
You can find your project DSN (Data Source Name) by visiting [sentry.io](sentry.io) and navigating to your [Project] > Settings > Client Keys (DSN).
</Banner>
- `enabled`: boolean | optional
Set to false to disable the plugin. Defaults to true.
- `init` : ClientOptions | optional
Sentry allows a variety of options to be passed into the Sentry.init() function, see the full list of options [here](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
- `requestHandler` : RequestHandlerOptions | optional
Accepts options that let you decide what data should be included in the event sent to Sentry, checkout the options [here](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
- `captureErrors`: number[] | optional
By default, `Sentry.errorHandler` will capture only errors with a status code of 500 or higher. To capture additional error codes, pass the values as numbers in an array.
To see all options available, visit the [Sentry Docs](https://docs.sentry.io/platforms/node/guides/express/configuration/options).
### Example
Configure any of these options by passing them to the plugin:
```ts
import { buildConfig } from 'payload/config'
import { sentry } from '@payloadcms/plugin-sentry'
import { Pages, Media } from './collections'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
sentry({
dsn: 'https://61edebas777689984d323d777@o4505289711681536.ingest.sentry.io/4505357433352176',
options: {
init: {
debug: true,
environment: 'development',
tracesSampleRate: 1.0,
},
requestHandler: {
serverName: false,
user: ['email'],
},
captureErrors: [400, 403, 404],
},
}),
],
})
export default config
```
## TypeScript
All types can be directly imported:
```ts
import { PluginOptions } from '@payloadcms/plugin-sentry/types'
```

View File

@@ -1,214 +0,0 @@
---
title: SEO Plugin
label: SEO
order: 20
desc: Manage SEO metadata from your Payload admin
keywords: plugins, seo, meta, search, engine, ranking, google
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-seo)](https://www.npmjs.com/package/@payloadcms/plugin-seo)
This plugin allows you to easily manage SEO metadata for your application from within your admin panel. When enabled on your collections and globals, it adds a new `meta` field group containing `title`, `description`, and `image` by default. Your front-end application can then use this data to render meta tags however your application requires. For example, you would inject a `title` tag into the `<head>` of your page using `meta.title` as its content.
As users are editing documents within the admin panel, they have the option to "auto-generate" these fields. When clicked, this plugin will execute your own custom functions that re-generate the title, description, and image. This way you can build your own SEO writing assistance directly into your application. For example, you could append your site name onto the page title, or use the document's excerpt field as the description, or even integrate with some third-party API to generate the image using AI.
To help you visualize what your page might look like in a search engine, a preview is rendered on page just beneath the meta fields. This preview is updated in real-time as you edit your metadata. There are also visual indicators to help you write effective meta, such as a character counter for the title and description fields. You can even inject your own custom fields into the `meta` field group as your application requires, like `og:title` or `json-ld`. If you've ever used something like Yoast SEO, this plugin might feel very familiar.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-seo). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-seo%3A) with as much detail as possible.
</Banner>
##### Core features
- Adds a `meta` field group to every SEO-enabled collection or global
- Allows you to define custom functions to auto-generate metadata
- Displays hints and indicators to help content editor write effective meta
- Renders a snippet of what a search engine might display
- Extendable so you can define custom fields like `og:title` or `json-ld`
- Soon will support dynamic variable injection
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-seo
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload/config';
import seoPlugin from '@payloadcms/plugin-seo';
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: []
},
{
slug: 'media',
upload: {
staticDir: // path to your static directory,
},
fields: []
}
],
plugins: [
seoPlugin({
collections: [
'pages',
],
uploadsCollection: 'media',
generateTitle: ({ doc }) => `Website.com — ${doc.title.value}`,
generateDescription: ({ doc }) => doc.excerpt
})
]
});
export default config;
```
### Options
##### `collections`
An array of collections slugs to enable SEO. Enabled collections receive a `meta` field which is an object of title, description, and image subfields.
##### `globals`
An array of global slugs to enable SEO. Enabled globals receive a `meta` field which is an object of title, description, and image subfields.
##### `fields`
An array of fields that allows you to inject your own custom fields onto the `meta` field group. The following fields are provided by default:
- `title`: text
- `description`: textarea
- `image`: upload (if an `uploadsCollection` is provided)
- `preview`: ui
##### `uploadsCollection`
Set the `uploadsCollection` to your application's upload-enabled collection slug. This is used to provide an `image` field on the `meta` field group.
##### `tabbedUI`
When the `tabbedUI` property is `true`, it appends an `SEO` tab onto your config using Payload's [Tabs Field](https://payloadcms.com/docs/fields/tabs). If your collection is not already tab-enabled, meaning the first field in your config is not of type `tabs`, then one will be created for you called `Content`. Defaults to `false`.
<Banner type="info">
If you wish to continue to use top-level or sidebar fields with `tabbedUI`, you must not let the default `Content` tab get created for you (see the note above). Instead, you must define the first field of your config with type `tabs` and place all other fields adjacent to this one.
</Banner>
##### `generateTitle`
A function that allows you to return any meta title, including from document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title?.value}`,
})
}
```
##### `generateDescription`
A function that allows you to return any meta description, including from document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt?.value
})
}
```
##### `generateImage`
A function that allows you to return any meta image, including from document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage?.value
})
}
```
##### `generateURL`
A function called by the search preview component to display the actual URL of your page.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateURL: ({ ...docInfo, doc, locale }) => `https://yoursite.com/${collection?.slug}/${doc?.slug?.value}`
})
}
```
#### `interfaceName`
Rename the meta group interface name that is generated for TypeScript and GraphQL.
```ts
// payload.config.ts
{
// ...
seoPlugin({
interfaceName: 'customInterfaceNameSEO'
})
}
```
#### `fieldOverrides`
Pass any valid field props to the base fields: Title, Description or Image.
```ts
// payload.config.ts
seoPlugin({
// ...
fieldOverrides: {
title: {
required: true,
},
description: {
localized: true,
},
},
})
```
## TypeScript
All types can be directly imported:
```ts
import {
PluginConfig,
GenerateTitle,
GenerateDescription
GenerateURL
} from '@payloadcms/plugin-seo/types';
```
## Examples
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
## Screenshots
![image](https://user-images.githubusercontent.com/70709113/163850633-f3da5f8e-2527-4688-bc79-17233307a883.png)

View File

@@ -1,291 +0,0 @@
---
title: Stripe Plugin
label: Stripe
order: 20
desc: Easily accept payments with Stripe
keywords: plugins, stripe, payments, ecommerce
---
[![NPM](https://img.shields.io/npm/v/@payloadcms/plugin-stripe)](https://www.npmjs.com/package/@payloadcms/plugin-stripe)
With this plugin you can easily integrate [Stripe](https://stripe.com) into Payload. Simply provide your Stripe credentials and this plugin will open up a two-way communication channel between the two platforms. This enables you to easily sync data back and forth, as well as proxy the Stripe REST API through Payload's access control. Use this plugin to completely offload billing to Stripe and retain full control over your application's data.
For example, you might be building an e-commerce or SaaS application, where you have a `products` or a `plans` collection that requires either a one-time payment or a subscription. You can to tie each of these products to Stripe, then easily subscribe to billing-related events to perform your application's business logic, such as active purchases or subscription cancellations.
To build a checkout flow on your front-end you can either use [Stripe Checkout](https://stripe.com/payments/checkout), or you can also build a completely custom checkout experience from scratch using [Stripe Web Elements](https://stripe.com/docs/payments/elements). Then to build fully custom, secure customer dashboards, you can leverage Payload's access control to restrict access to your Stripe resources so your users never have to leave your site to manage their accounts.
The beauty of this plugin is the entirety of your application's content and business logic can be handled in Payload while Stripe handles solely the billing and payment processing. You can build a completely proprietary application that is endlessly customizable and extendable, on APIs and databases that you own. Hosted services like Shopify or BigCommerce might fracture your application's content then charge you for access.
<Banner type="info">
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-stripe). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20stripe&template=bug_report.md&title=plugin-stripe%3A) with as much detail as possible.
</Banner>
##### Core features
- Hides your Stripe credentials when shipping SaaS applications
- Allows restricted keys through [Payload access control](https://payloadcms.com/docs/access-control/overview)
- Enables a two-way communication channel between Stripe and Payload
- Proxies the [Stripe REST API](https://stripe.com/docs/api)
- Proxies [Stripe webhooks](https://stripe.com/docs/webhooks)
- Automatically syncs data between the two platforms
## Installation
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
```bash
yarn add @payloadcms/plugin-stripe
```
## Basic Usage
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload/config'
import stripePlugin from '@payloadcms/plugin-stripe'
const config = buildConfig({
plugins: [
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
}),
],
})
export default config
```
### Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `stripeSecretKey` \* | string | `undefined` | Your Stripe secret key |
| `stripeWebhooksEndpointSecret` | string | `undefined` | Your Stripe webhook endpoint secret |
| `rest` | boolean | `false` | When `true`, opens the `/api/stripe/rest` endpoint |
| `webhooks` | object \| function | `undefined` | Either a function to handle all webhooks events, or an object of Stripe webhook handlers, keyed to the name of the event |
| `sync` | array | `undefined` | An array of sync configs |
| `logs` | boolean | `false` | When `true`, logs sync events to the console as they happen |
_\* An asterisk denotes that a property is required._
## Endpoints
The following custom endpoints are automatically opened for you:
| Endpoint | Method | Description |
| --- | --- | --- |
| `/api/stripe/rest` | `POST` | Proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. See the [REST Proxy](#stripe-rest-proxy) section for more details. |
| `/stripe/webhooks` | `POST` | Handles all Stripe webhook events |
##### Stripe REST Proxy
If `rest` is true, proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. If you need to proxy the API server-side, use the [stripeProxy](#node) function.
```ts
const res = await fetch(`/api/stripe/rest`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
// Authorization: `JWT ${token}` // NOTE: do this if not in a browser (i.e. curl or Postman)
},
body: JSON.stringify({
stripeMethod: 'stripe.subscriptions.list',
stripeArgs: [
{
customer: 'abc',
},
],
}),
})
```
<Banner type="info">
<strong>Note:</strong>
<br />
The `/api` part of these routes may be different based on the settings defined in your Payload config.
</Banner>
## Webhooks
[Stripe webhooks](https://stripe.com/docs/webhooks) are used to sync from Stripe to Payload. Webhooks listen for events on your Stripe account so you can trigger reactions to them. Follow the steps below to enable webhooks.
Development:
1. Login using Stripe cli `stripe login`
1. Forward events to localhost `stripe listen --forward-to localhost:3000/stripe/webhooks`
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
Production:
1. Login and [create a new webhook](https://dashboard.stripe.com/test/webhooks/create) from the Stripe dashboard
1. Paste `YOUR_DOMAIN_NAME/stripe/webhooks` as the "Webhook Endpoint URL"
1. Select which events to broadcast
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
1. Then, handle these events using the `webhooks` portion of this plugin's config:
```ts
import { buildConfig } from 'payload/config'
import stripePlugin from '@payloadcms/plugin-stripe'
const config = buildConfig({
plugins: [
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
webhooks: {
'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
// do something...
},
},
// NOTE: you can also catch all Stripe webhook events and handle the event types yourself
// webhooks: (event, stripe, stripeConfig) => {
// switch (event.type): {
// case 'customer.subscription.updated': {
// // do something...
// break;
// }
// default: {
// break;
// }
// }
// }
}),
],
})
export default config
```
For a full list of available webhooks, see [here](https://stripe.com/docs/cli/trigger#trigger-event).
## Node
On the server you should interface with Stripe directly using the [stripe](https://www.npmjs.com/package/stripe) npm module. That might look something like this:
```ts
import Stripe from 'stripe'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
const stripe = new Stripe(stripeSecretKey, { apiVersion: '2022-08-01' })
export const MyFunction = async () => {
try {
const customer = await stripe.customers.create({
email: data.email,
})
// do something...
} catch (error) {
console.error(error.message)
}
}
```
Alternatively, you can interface with the Stripe using the `stripeProxy`, which is exactly what the `/api/stripe/rest` endpoint does behind-the-scenes. Here's the same example as above, but piped through the proxy:
```ts
import { stripeProxy } from '@payloadcms/plugin-stripe'
export const MyFunction = async () => {
try {
const customer = await stripeProxy({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeMethod: 'customers.create',
stripeArgs: [
{
email: data.email,
},
],
})
if (customer.status === 200) {
// do something...
}
if (customer.status >= 400) {
throw new Error(customer.message)
}
} catch (error) {
console.error(error.message)
}
}
```
## Sync
This option will setup a basic sync between Payload collections and Stripe resources for you automatically. It will create all the necessary hooks and webhooks handlers, so the only thing you have to do is map your Payload fields to their corresponding Stripe properties. As documents are created, updated, and deleted from either Stripe or Payload, the changes are reflected on either side.
<Banner type="info">
<strong>Note:</strong>
<br />
If you wish to enable a _two-way_ sync, be sure to setup [`webhooks`](#webhooks) and pass the `stripeWebhooksEndpointSecret` through your config.
</Banner>
```ts
import { buildConfig } from 'payload/config'
import stripePlugin from '@payloadcms/plugin-stripe'
const config = buildConfig({
plugins: [
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
sync: [
{
collection: 'customers',
stripeResourceType: 'customers',
stripeResourceTypeSingular: 'customer',
fields: [
{
fieldPath: 'name', // this is a field on your own Payload config
stripeProperty: 'name', // use dot notation, if applicable
},
],
},
],
}),
],
})
export default config
```
<Banner type="warning">
<strong>Note:</strong>
<br />
Due to limitations in the Stripe API, this currently only works with top-level fields. This is because every Stripe object is a separate entity, making it difficult to abstract into a simple reusable library. In the future, we may find a pattern around this. But for now, cases like that will need to be hard-coded.
</Banner>
Using `sync` will do the following:
- Adds and maintains a `stripeID` read-only field on each collection, this is a field generated _by Stripe_ and used as a cross-reference
- Adds a direct link to the resource on Stripe.com
- Adds and maintains an `skipSync` read-only flag on each collection to prevent infinite syncs when hooks trigger webhooks
- Adds the following hooks to each collection:
- `beforeValidate`: `createNewInStripe`
- `beforeChange`: `syncExistingWithStripe`
- `afterDelete`: `deleteFromStripe`
- Handles the following Stripe webhooks
- `STRIPE_TYPE.created`: `handleCreatedOrUpdated`
- `STRIPE_TYPE.updated`: `handleCreatedOrUpdated`
- `STRIPE_TYPE.deleted`: `handleDeleted`
## TypeScript
All types can be directly imported:
```ts
import {
StripeConfig,
StripeWebhookHandler,
StripeProxy,
...
} from '@payloadcms/plugin-stripe/types';
```
## Examples
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. You can also check out [How to Build An E-Commerce Site With Next.js](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs) post for a bit more context around this template.

View File

@@ -1,288 +0,0 @@
---
title: Production Deployment
label: Deployment
order: 10
desc: When your Payload based app is ready, tested, looking great, it is time to deploy. Learn how to deploy your app and what to consider before deployment.
keywords: deployment, production, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner type="success">
So you've developed a Payload app, it's fully tested, and running great locally. Now it's time to
launch. <strong>Awesome! Great work!</strong> Now, what's next?
</Banner>
There are many ways to deploy Payload to a production environment. When evaluating how you will deploy Payload, you need
to consider these main aspects:
1. [Basics](#basics)
1. [Security](#security)
1. [Your MongoDB](#mongodb)
1. [Permanent File Storage](#file-storage)
1. [Docker](#docker)
## 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.
## Security
Payload features a suite of security features that you can rely on to strengthen your application's security. When
deploying to Production, it's a good idea to double-check that you are making proper use of each of them.
##### 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.
##### Double-check and thoroughly test all Access Control
Because _**you**_ are in complete control of who can do what with your data, you should double and triple-check that you
wield that power responsibly before deploying to Production.
<Banner type="error">
<strong>By default, all Access Control functions require that a user is successfully logged in to Payload to create, read, update, or delete data.</strong>{' '}
But, if you allow public user registration, for example, you will want to make sure that your
access control functions are more strict - permitting <strong>only appropriate users</strong> to
perform appropriate actions.
</Banner>
##### Building the Admin panel
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:
`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 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.
##### Secure Cookie Settings
You should be using an SSL certificate for production Payload instances, which means you
can [enable secure cookies](/docs/authentication/config) in your Authentication-enabled Collection configs.
##### 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
more. [Click here to learn more](/docs/production/preventing-abuse).
## MongoDB
Payload can be used with any MongoDB compatible database including AWS DocumentDB or Azure Cosmos DB.
##### 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>
##### DocumentDB
When using AWS DocumentDB, you will need to configure connection options for authentication in the `connectOptions`
passed to the `mongooseAdapter` . You also need to set `connectOptions.useFacet` to `false` to disable use of the
unsupported `$facet` aggregation.
##### CosmosDB
When using Azure Cosmos DB, an index is needed for any field you may want to sort on. To add the sort index for all
fields that may be sorted in the admin UI use the <a href="/docs/configuration/overview">indexSortableFields</a>
configuration option.
## File storage
If you are using Payload to [manage file uploads](/docs/upload/overview), you need to consider where your uploaded files
will be permanently stored. If you do not use Payload for file uploads, then this section does not impact your app
whatsoever.
#### Persistent vs Ephemeral Filesystems
Some cloud app hosts such as [Heroku](https://heroku.com) use `ephemeral` file systems, which means that any files
uploaded to your server only last until the server restarts or shuts down. Heroku and similar providers schedule
restarts and shutdowns without your control, meaning your uploads will accidentally disappear without any way to get
them back.
Alternatively, persistent filesystems will never delete your files and can be trusted to reliably host uploads
perpetually.
**Popular cloud providers with ephemeral filesystems:**
- Heroku
- DigitalOcean Apps
**Popular cloud providers with persistent filesystems:**
- DigitalOcean Droplets
- Amazon EC2
- GoDaddy
- Many other more traditional web hosts
<Banner type="error">
<strong>Warning:</strong>
<br />
If you rely on Payload's <strong>Upload</strong> functionality, make sure you either use a host
with a persistent filesystem or have an integration with a third-party file host like Amazon S3.
</Banner>
##### Using ephemeral filesystem providers like Heroku
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.
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.
**To automatically send uploaded files to S3 or similar, you could:**
- Write an asynchronous `beforeChange` hook for all Collections that support Uploads, which takes any uploaded `file`
from the Express `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
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).
## Docker
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
```dockerfile
FROM node:18-alpine as base
FROM base as builder
WORKDIR /home/node
COPY package*.json ./
COPY . .
RUN yarn install
RUN yarn build
FROM base as runtime
ENV NODE_ENV=production
WORKDIR /home/node
COPY package*.json ./
RUN yarn install --production
COPY --from=builder /home/node/dist ./dist
COPY --from=builder /home/node/build ./build
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
## Docker Compose
Here is an example of a docker-compose.yml file that can be used for development
```yml
version: '3'
services:
payload:
image: node:18-alpine
ports:
- '3000:3000'
volumes:
- .:/home/node/app
- node_modules:/home/node/app/node_modules
working_dir: /home/node/app/
command: sh -c "yarn install && yarn dev"
depends_on:
- mongo
environment:
DATABASE_URI: mongodb://mongo:27017/payload
PORT: 3000
NODE_ENV: development
PAYLOAD_SECRET: TESTING
mongo:
image: mongo:latest
ports:
- '27017:27017'
command:
- --storageEngine=wiredTiger
volumes:
- data:/data/db
logging:
driver: none
volumes:
data:
node_modules:
```

View File

@@ -1,66 +0,0 @@
---
title: Preventing Production API Abuse
label: Preventing Abuse
order: 20
desc: Payload has built-in security that can be configured to combat production API abuse such as limiting login attempts and IP requests.
keywords: abuse, production, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
## Introduction
Payload has built-in security best practices that can be configured to your application-specific needs.
### Limit Failed Login Attempts
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 `900000` (15 minutes). |
| **`max`** | Number of requests served from a single IP before limiting. Defaults to `500`. |
| **`skip`** | Express middleware 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`.
### Cross-Site Request Forgery (CSRF)
CSRF prevention will verify the authenticity of each request to your API to prevent a malicious action from another site from authorized users. See how to configure CSRF [here](/docs/authentication/overview#csrf-protection).
### Cross Origin Resource Sharing (CORS)
To securely allow headless operation you will need to configure the allowed origins for requests to be able to use the Payload API. You can see how to set CORS as well as other payload configuration settings [here](/docs/configuration/overview)
### Limiting GraphQL Complexity
Because GraphQL gives the power of query writing outside a server's control, someone with bad intentions might write a maliciously complex query and bog down your server. To prevent resource-intensive GraphQL requests, Payload provides a way specify complexity limits which are based on a complexity score that is calculated for each request.
Any GraphQL request that is calculated to be too expensive is rejected. On the Payload config, in `graphQL` you can set the `maxComplexity` value as an integer. For reference, the default complexity value for each added field is 1, and all `relationship` and `upload` fields are assigned a value of 10.
If you do not need GraphQL it is advised that you disable it altogether with the Payload config by setting `graphQL.disable: true`. Should you wish to enable GraphQL again, you can remove this property or set it `false`, any time. By turning it off, Payload will bypass creating schemas from your collections and will not register the express route.
### Malicious File Uploads
Payload does not execute uploaded files on the server, but depending on your setup it may be used to transmit and store potentially dangerous files. If your configuration allows file uploads there is the potential that a bad actor uploads a malicious file that is then served to other users. Consider the following ways to mitigate the risks.
First, enable email [verification](/docs/authentication/config#email-verification) when users are allowed to register new accounts and add other bot prevention services.
Review that `create` and `update` access on file upload collections are as restrictive as your application needs allow. Consider limiting `read` access of uploaded user's files and how you might limit user uploaded files from being served outside of Payload.
You can also add a [3rd party library](https://github.com/Cisco-Talos/clamav) to scan files in a [hook](/docs/hooks/collections) or have antivirus software in place.

View File

@@ -1,227 +0,0 @@
---
title: Querying your Documents
label: Overview
order: 10
desc: Payload provides a querying language through all APIs, allowing you to filter or search through documents within a Collection.
keywords: query, documents, overview, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload provides an extremely granular querying language through all APIs. Each API takes the same syntax and fully supports all options.
<Banner>
<strong>Here, "querying" relates to filtering or searching through documents within a Collection.</strong>{' '}
You can build queries to pass to Find operations as well as to [restrict which documents certain
users can access](/docs/access-control/overview) via access control functions.
</Banner>
### Simple queries
For example, say you have a collection as follows:
```ts
import { CollectionConfig } from 'payload/types'
export const Post: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'color',
type: 'select',
options: ['mint', 'dark-gray', 'white'],
},
{
name: 'featured',
type: 'checkbox',
},
],
}
```
You may eventually have a lot of documents within this Collection. If you wanted to find only documents with `color` equal to `mint`, you could write a query as follows:
```js
const query = {
color: {
// property name to filter on
equals: 'mint', // operator to use and value to compare against
},
}
```
The above example demonstrates a simple query but you can get much more complex.
### Operators
| Operator | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `equals` | The value must be exactly equal. |
| `not_equals` | The query will return all documents where the value is not equal. |
| `greater_than` | For numeric or date-based fields. |
| `greater_than_equal` | For numeric or date-based fields. |
| `less_than` | For numeric or date-based fields. |
| `less_than_equal` | For numeric or date-based fields. |
| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. |
| `contains` | Must contain the value entered, case-insensitive. |
| `in` | The value must be found within the provided comma-delimited list of values. |
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
| `all` | The value must contain all values provided in the comma-delimited list. |
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
| `near` | For distance related to a [point field](/docs/fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
<Banner type="success">
<strong>Tip</strong>:<br />
If you know your users will be querying on certain fields a lot, you can add <strong>
index: true
</strong> to a field's config which will speed up searches using that field immensely.
</Banner>
### And / Or Logic
In addition to defining simple queries, you can join multiple queries together using simple AND / OR logic. Let's take the above `Post` collection for example and write a more complex query using AND / OR:
```js
const query = {
or: [
// array of OR conditions
{
color: {
equals: 'mint',
},
},
{
and: [
// nested array of AND conditions
{
color: {
equals: 'white',
},
},
{
featured: {
equals: false,
},
},
],
},
],
}
```
Written in plain English, if the above query were passed to a `find` operation, it would translate to finding posts where either the `color` is `mint` OR the `color` is `white` AND `featured` is set to false.
### Nested properties
When working with nested properties, which can happen when using relational fields, it is possible to use the dot notation to access the nested property. For example, when working with a `Song` collection that has a `artists` field which is related to an `Artists` collection using the `name: 'artists'`. You can access a property within the collection `Artists` like so:
```js
const query = {
'artists.featured': {
// nested property name to filter on
exists: true, // operator to use and boolean value that needs to be true
},
}
```
### GraphQL Find Queries
All GraphQL `find` queries support the `where` argument, which accepts queries exactly as detailed above.
**For example:**
```
query {
Posts(where: { color: { equals: mint } }) {
docs {
color
}
totalDocs
}
}
```
### REST Queries
With the REST API, you can use the full power of Payload queries as well but they become a bit more unwieldy the more complex that they get.
Simple queries are fairly straightforward to write. To understand the syntax, you need to understand how Express and similar languages would go about parsing a complex URL search string into a JSON object. For example, the above [simple query](#simple-queries) would be parsed into a string like this:
**`https://localhost:3000/api/posts?where[color][equals]=mint`**
This one isn't too bad, but more complex queries get unavoidably more difficult to write as query strings. For this reason, we recommend to use the extremely helpful and ubiquitous [`qs`](https://www.npmjs.com/package/qs) package to parse your JSON / object-formatted queries into query strings for use with the REST API.
**For example, using fetch:**
```js
import qs from 'qs'
const query = {
color: {
equals: 'mint',
},
// This query could be much more complex
// and QS would handle it beautifully
}
const getPosts = async () => {
const stringifiedQuery = qs.stringify(
{
where: query, // ensure that `qs` adds the `where` property, too!
},
{ addQueryPrefix: true },
)
const response = await fetch(`http://localhost:3000/api/posts${stringifiedQuery}`)
// Continue to handle the response below...
}
```
### Local API Queries
The Local API's `find` operation accepts an object exactly how you write it. For example:
```js
const getPosts = async () => {
const posts = await payload.find({
collection: 'posts',
where: {
color: {
equals: 'mint',
},
},
})
return posts
}
```
## Sort
Payload `find` queries support a `sort` parameter through all APIs. Pass the `name` of a top-level field to sort by that field in ascending order. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Because sorting is handled by the database, the field you wish to sort on must be stored in the database to work; not a [virtual field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It is recommended to enable indexing for the fields where sorting is used.
**REST example:**
**`https://localhost:3000/api/posts?sort=-createdAt`**
**GraphQL example:**
```
query {
Posts(sort: "-createdAt") {
docs {
color
}
}
}
```
**Local API example:**
```js
const getPosts = async () => {
const posts = await payload.find({
collection: 'posts',
sort: '-createdAt',
})
return posts
}
```

View File

@@ -1,65 +0,0 @@
---
title: Pagination
label: Pagination
order: 20
desc: Payload queries are equipped with automatic pagination so you create paginated lists of documents within your app.
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, express
---
All collection `find` queries are paginated automatically. Responses are returned with top-level meta data related to pagination, and returned documents are nested within a `docs` array.
**`Find` response properties:**
| Property | Description |
| ------------- | --------------------------------------------------------- |
| docs | Array of documents in the collection |
| totalDocs | Total available documents within the collection |
| limit | Limit query parameter - defaults to `10` |
| totalPages | Total pages available, based upon the `limit` queried for |
| page | Current page number |
| pagingCounter | `number` of the first doc on the current page |
| hasPrevPage | `true/false` if previous page exists |
| hasNextPage | `true/false` if next page exists |
| prevPage | `number` of previous page, `null` if it doesn't exist |
| nextPage | `number` of next page, `null` if it doesn't exist |
**Example response:**
```json
{
// Document Array // highlight-line
"docs": [
{
"title": "Page Title",
"description": "Some description text",
"priority": 1,
"createdAt": "2020-10-17T01:19:29.858Z",
"updatedAt": "2020-10-17T01:19:29.858Z",
"id": "5f8a46a1dd05db75c3c64760"
}
],
// Metadata // highlight-line
"totalDocs": 6,
"limit": 1,
"totalPages": 6,
"page": 1,
"pagingCounter": 1,
"hasPrevPage": false,
"hasNextPage": true,
"prevPage": null,
"nextPage": 2
}
```
## Pagination controls
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
| Control | Description |
| ------- | --------------------------------------- |
| `limit` | Limits the number of documents returned |
| `page` | Get a specific page number |
### Disabling pagination within Local API
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation.

View File

@@ -1,620 +0,0 @@
---
title: REST API
label: Overview
order: 10
desc: Payload generates a fully functional REST API from your Collection and Global configs.
keywords: rest, api, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
A fully functional REST API is automatically generated from your Collection and Global configs.
</Banner>
All Payload API routes are mounted and prefixed to your config's `routes.api` URL segment (default: `/api`).
**REST query parameters:**
- [depth](/docs/getting-started/concepts#depth) - automatically populates relationships and uploads
- [locale](/docs/configuration/localization#retrieving-localized-docs) - retrieves document(s) in a specific locale
- [fallback-locale](/docs/configuration/localization#retrieving-localized-docs) - specifies a fallback locale if no locale value exists
## Collections
Each collection is mounted using its `slug` value. For example, if a collection's slug is `users`, all corresponding routes will be mounted on `/api/users`.
Note: Collection slugs must be formatted in kebab-case
**All CRUD operations are exposed as follows:**
<RestExamples
data={[
{
operation: "Find",
method: "GET",
path: "/api/{collection-slug}",
description: "Find paginated documents",
example: {
slug: "getCollection",
req: true,
res: {
paginated: true,
data: {
id: "644a5c24cc1383022535fc7c",
title: "Home",
content: "REST API examples",
slug: "home",
createdAt: "2023-04-27T11:27:32.419Z",
updatedAt: "2023-04-27T11:27:32.419Z",
},
},
drawerContent: (
<>
<h6>Additional <code>find</code> query parameters</h6>
The <code>find</code> endpoint supports the following additional query parameters:
<ul>
<li>
<a href="/docs/queries/overview#sort">sort</a> - sort by field
</li>
<li>
<a href="/docs/queries/overview">where</a> - pass a where query to constrain returned
documents
</li>
<li>
<a href="/docs/queries/pagination#pagination-controls">limit</a> - limit the returned
documents to a certain number
</li>
<li>
<a href="/docs/queries/pagination#pagination-controls">page</a> - get a specific page of
documents
</li>
</ul>
</>
),
},
},
{
operation: "Find By ID",
method: "GET",
path: "/api/{collection-slug}/{id}",
description: "Find a specific document by ID",
example: {
slug: "findByID",
req: true,
res: {
id: "644a5c24cc1383022535fc7c",
title: "Home",
content: "REST API examples",
slug: "home",
createdAt: "2023-04-27T11:27:32.419Z",
updatedAt: "2023-04-27T11:27:32.419Z",
},
},
},
{
operation: "Count",
method: "GET",
path: "/api/{collection-slug}/count",
description: "Count the documents",
example: {
slug: "count",
req: true,
res: {
totalDocs: 10
},
},
},
{
operation: "Create",
method: "POST",
path: "/api/{collection-slug}",
description: "Create a new document",
example: {
slug: "createDocument",
req: {
credentials: true,
headers: true,
body: {
title: "New page",
content: "Here is some content",
},
},
res: {
message: "Page successfully created.",
doc: {
id: "644ba34c86359864f9535932",
title: "New page",
content: "Here is some content",
slug: "new-page",
createdAt: "2023-04-28T10:43:24.466Z",
updatedAt: "2023-04-28T10:43:24.466Z",
},
},
},
},
{
operation: "Update",
method: "PATCH",
path: "/api/{collection-slug}",
description: "Update all documents matching the where query",
example: {
slug: "updateDocument",
req: {
credentials: true,
query: true,
headers: true,
body: {
title: "I have been updated!",
},
},
res: {
docs: [
{
id: "644ba34c86359864f9535932",
title: "I have been updated!",
content: "Here is some content",
slug: "new-page",
createdAt: "2023-04-28T10:43:24.466Z",
updatedAt: "2023-04-28T10:45:23.724Z",
},
],
errors: [],
},
},
},
{
operation: "Update By ID",
method: "PATCH",
path: "/api/{collection-slug}/{id}",
description: "Update a document by ID",
example: {
slug: "updateDocumentByID",
req: {
credentials: true,
headers: true,
body: {
title: "I have been updated by ID!",
categories: "example-uuid",
tags: {
relationTo: "location",
value: "another-example-uuid",
},
},
},
res: {
message: "Updated successfully.",
doc: {
id: "644a5c24cc1383022535fc7c",
title: "I have been updated by ID!",
content: "REST API examples",
categories: {
id: "example-uuid",
name: "Test Category",
},
tags: [
{
relationTo: "location",
value: {
id: "another-example-uuid",
name: "Test Location",
},
},
],
slug: "home",
createdAt: "2023-04-27T11:27:32.419Z",
updatedAt: "2023-04-28T10:47:59.259Z",
},
},
},
},
{
operation: "Delete",
method: "DELETE",
path: "/api/{collection-slug}",
description: "Delete all documents matching the where query",
example: {
slug: "deleteDocuments",
req: {
credentials: true,
query: true,
headers: true,
},
res: {
docs: [
{
id: "644ba4cf86359864f953594b",
title: "New page",
content: "Here is some content",
slug: "new-page",
createdAt: "2023-04-28T10:49:51.359Z",
updatedAt: "2023-04-28T10:49:51.359Z",
},
],
errors: [],
},
},
},
{
operation: "Delete by ID",
method: "DELETE",
path: "/api/{collection-slug}/{id}",
description: "Delete an existing document by ID",
example: {
slug: "deleteByID",
req: {
credentials: true,
headers: true,
},
res: {
id: "644ba51786359864f9535954",
title: "New page",
content: "Here is some content",
slug: "new-page",
createdAt: "2023-04-28T10:51:03.028Z",
updatedAt: "2023-04-28T10:51:03.028Z",
},
},
},
]}
/>
## Auth Operations
Auth enabled collections are also given the following endpoints:
<RestExamples
data={[
{
operation: "Login",
method: "POST",
path: "/api/{user-collection}/login",
description: "Logs in a user with email / password",
example: {
slug: "login",
req: {
credentials: true,
headers: true,
body: {
email: "dev@payloadcms.com",
password: "password",
},
},
res: {
message: "Auth Passed",
user: {
id: "644b8453cd20c7857da5a9b0",
email: "dev@payloadcms.com",
_verified: true,
createdAt: "2023-04-28T08:31:15.788Z",
updatedAt: "2023-04-28T11:11:03.716Z",
},
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
exp: 1682689147,
},
},
},
{
operation: "Logout",
method: "POST",
path: "/api/{user-collection}/logout",
description: "Logs out a user",
example: {
slug: "logout",
req: {
headers: true,
credentials: true,
},
res: {
message: "You have been logged out successfully.",
},
},
},
{
operation: "Unlock",
method: "POST",
path: "/api/{user-collection}/unlock",
description: "Unlock a user account",
example: {
slug: "unlockCollection",
req: {
credentials: true,
headers: true,
body: {
email: "dev@payloadcms.com",
},
},
res: {
message: "Success",
},
},
},
{
operation: "Refresh",
method: "POST",
path: "/api/{user-collection}/refresh-token",
description: "Refreshes a token that has not yet expired",
example: {
slug: "refreshToken",
req: {
credentials: true,
headers: true,
},
res: {
message: "Token refresh successful",
refreshedToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
exp: 1682689362,
user: {
email: "dev@payloadcms.com",
id: "644b8453cd20c7857da5a9b0",
collection: "users",
},
},
},
},
{
operation: "Verify User",
method: "POST",
path: "/api/{user-collection}/verify/{token}",
description: "User verification",
example: {
slug: "verifyUser",
req: {
credentials: true,
headers: true,
},
res: {
message: "Email verified successfully.",
},
},
},
{
operation: "Current User",
method: "GET",
path: "/api/{user-collection}/me",
description: "Returns the currently logged in user with token",
example: {
slug: "currentUser",
req: {
credentials: true,
headers: true,
},
res: {
user: {
id: "644b8453cd20c7857da5a9b0",
email: "dev@payloadcms.com",
_verified: true,
createdAt: "2023-04-28T08:31:15.788Z",
updatedAt: "2023-04-28T11:45:23.926Z",
_strategy: "local-jwt",
},
collection: "users",
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
exp: 1682689523,
},
},
},
{
operation: "Forgot Password",
method: "POST",
path: "/api/{user-collection}/forgot-password",
description: "Password reset workflow entry point",
example: {
slug: "forgotPassword",
req: {
headers: true,
credentials: true,
body: {
email: "dev@payloadcms.com",
},
},
res: {
message: "Success",
},
},
},
{
operation: "Reset Password",
method: "POST",
path: "/api/{user-collection}/reset-password",
description: "Reset user password",
example: {
slug: "resetPassword",
req: {
credentials: true,
headers: true,
body: {
token: "7eac3830ffcfc7f9f66c00315dabeb11575dba91",
password: "newPassword",
},
},
res: {
message: "Password reset successfully.",
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
user: {
id: "644baa473ea9538765cc30fc",
email: "dev@payloadcms.com",
_verified: true,
createdAt: "2023-04-28T11:13:11.569Z",
updatedAt: "2023-04-28T11:49:23.860Z",
},
},
},
},
]}
/>
## Globals
Globals cannot be created or deleted, so there are only two REST endpoints opened:
<RestExamples
data={[
{
operation: 'Get Global',
method: 'GET',
path: '/api/globals/{global-slug}',
description: 'Get a global by slug',
example: {
slug: 'getGlobal',
req: {
credentials: true,
headers: true,
},
res: {
announcement: 'Here is an announcement!',
globalType: 'announcement',
createdAt: '2023-04-28T08:53:56.066Z',
updatedAt: '2023-04-28T08:53:56.066Z',
id: '644b89a496c64a833fe579c9',
},
},
},
{
operation: 'Update Global',
method: 'POST',
path: '/api/globals/{global-slug}',
description: 'Update a global by slug',
example: {
slug: 'updateGlobal',
req: {
headers: true,
credentials: true,
body: {
announcement: 'Paging Doctor Scrunt',
},
},
res: {
announcement: 'Paging Doctor Scrunt',
globalType: 'announcement',
createdAt: '2023-04-28T08:53:56.066Z',
updatedAt: '2023-04-28T08:53:56.066Z',
id: '644b89a496c64a833fe579c9',
},
},
},
]}
/>
## Preferences
In addition to the dynamically generated endpoints above Payload also has REST endpoints to manage the admin user [preferences](/docs/admin/overview#preferences) for data specific to the authenticated user.
<RestExamples
data={[
{
operation: 'Get Preference',
method: 'GET',
path: '/api/payload-preferences/{key}',
description: 'Get a preference by key',
example: {
slug: 'getPreference',
req: {
headers: true,
credentials: true,
},
res: {
_id: '644bb7a8307b3d363c6edf2c',
key: 'region',
user: '644b8453cd20c7857da5a9b0',
userCollection: 'users',
__v: 0,
createdAt: '2023-04-28T12:10:16.689Z',
updatedAt: '2023-04-28T12:10:16.689Z',
value: 'Europe/London',
},
},
},
{
operation: 'Create Preference',
method: 'POST',
path: '/api/payload-preferences/{key}',
description: 'Create or update a preference by key',
example: {
slug: 'createPreference',
req: {
headers: true,
credentials: true,
body: {
value: 'Europe/London',
},
},
res: {
message: 'Updated successfully.',
doc: {
user: '644b8453cd20c7857da5a9b0',
key: 'region',
userCollection: 'users',
value: 'Europe/London',
},
},
},
},
{
operation: 'Delete Preference',
method: 'DELETE',
path: '/api/payload-preferences/{key}',
description: 'Delete a preference by key',
example: {
slug: 'deletePreference',
req: {
headers: true,
},
res: {
message: 'deletedSuccessfully',
},
},
},
]}
/>
## Custom Endpoints
Additional REST API endpoints can be added to your application by providing an array of `endpoints` in various places within a Payload config. Custom endpoints are useful for adding additional middleware on existing routes or for building custom functionality into Payload apps and plugins. Endpoints can be added at the top of the Payload config, `collections`, and `globals` and accessed respective of the api and slugs you have configured.
Each endpoint object needs to have:
| Property | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`path`** | A string for the endpoint route after the collection or globals slug |
| **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' |
| **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) |
| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
Example:
```ts
import { CollectionConfig } from 'payload/types'
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
export const Orders: CollectionConfig = {
slug: 'orders',
fields: [
/* ... */
],
// highlight-start
endpoints: [
{
path: '/:id/tracking',
method: 'get',
handler: async (req, res, next) => {
const tracking = await getTrackingInfo(req.params.id)
if (tracking) {
res.status(200).send({ tracking })
} else {
res.status(404).send({ error: 'not found' })
}
},
},
],
// highlight-end
}
```
<Banner>
<strong>Note:</strong>
<br />
**req** will have the **payload** object and can be used inside your endpoint handlers for making
calls like req.payload.find() that will make use of access control and hooks.
</Banner>

View File

@@ -1,646 +0,0 @@
---
title: Lexical Rich Text
label: Lexical
order: 30
desc: Built by Meta, Lexical is an incredibly powerful rich text editor, and it works beautifully within Payload.
keywords: lexical, rich text, editor, headless cms
---
One of Payload's goals is to build the best rich text editor experience that we possibly can. We want to combine the beauty and polish of the Medium editing experience with the strength and features of the Notion editor - all in one place.
Classically, we've used SlateJS to work toward this goal, but building custom elements into Slate has proven to be more difficult than we'd like, and we've been keeping our options open.
<Banner type="warning">
Payload's Lexical rich text editor is currently in beta. It's stable enough to use as you build on Payload, so if you're up for helping us fine-tune it, you should use it. But if you're looking for stability, use Slate instead.
</Banner>
Lexical is extremely impressive and trivializes a lot of the hard parts of building new elements into a rich text editor. It has a few distinct advantages over Slate, including the following:
1. A "/" menu, which allows editors to easily add new elements while never leaving their keyboard
1. A "hover" toolbar that pops up if you select text
1. It supports Payload blocks natively, directly within your rich text editor
1. Custom elements, called "features", are much easier to build in Lexical vs. Slate
To use the Lexical editor, first you need to install it:
```
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 { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
collections: [
// your collections here
],
// Pass the Lexical editor to the root config
editor: lexicalEditor({})
})
```
You can also override Lexical settings on a field-by-field basis as follows:
```ts
import type { CollectionConfig } from 'payload/types'
import {
lexicalEditor
} from '@payloadcms/richtext-lexical'
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'content',
type: 'richText',
// Pass the Lexical editor here and override base settings as necessary
editor: lexicalEditor({})
}
]
}
```
## Extending the lexical editor with Features
Lexical has been designed with extensibility in mind. Whether you're aiming to introduce new functionalities or tweak the existing ones, Lexical makes it seamless for you to bring those changes to life.
### Features: The Building Blocks
At the heart of Lexical's customization potential are "features". While Lexical ships with a set of default features we believe are essential for most use cases, the true power lies in your ability to redefine, expand, or prune these as needed.
If you remove all the default features, you're left with a blank editor. You can then add in only the features you need, or you can build your own custom features from scratch.
### Integrating New Features
To weave in your custom features, utilize the `features` prop when initializing the Lexical Editor. Here's a basic example of how this is done:
```ts
import {
BlocksFeature,
LinkFeature,
UploadFeature,
lexicalEditor
} from '@payloadcms/richtext-lexical'
import { Banner } from '../blocks/Banner'
import { CallToAction } from '../blocks/CallToAction'
{
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
LinkFeature({
// Example showing how to customize the built-in fields
// of the Link feature
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
admin: {
description:
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
}),
UploadFeature({
collections: {
uploads: {
// Example showing how to customize the built-in fields
// of the Upload feature
fields: [
{
name: 'caption',
type: 'richText',
editor: lexicalEditor(),
},
],
},
},
}),
// This is incredibly powerful. You can re-use your Payload blocks
// directly in the Lexical editor as follows:
BlocksFeature({
blocks: [
Banner,
CallToAction,
],
}),
]
})
}
```
## Features overview
Here's an overview of all the included features:
| Feature Name | Included by default | Description |
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`BoldTextFeature`** | Yes | Handles the bold text format |
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
| **`StrikethroughTextFeature`** | Yes | Handles the strikethrough text format |
| **`SubscriptTextFeature`** | Yes | Handles the subscript text format |
| **`SuperscriptTextFeature`** | Yes | Handles the superscript text format |
| **`InlineCodeTextFeature`** | Yes | Handles the inline-code text format |
| **`ParagraphFeature`** | Yes | Handles paragraphs. Since they are already a key feature of lexical itself, this Feature mainly handles the Slash and Add-Block menu entries for paragraphs |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`CheckListFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
## 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.
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!
## Converters
### Lexical => HTML
Lexical saves data in JSON, but can also generate its HTML representation via two main methods:
1. **Outputting HTML from the Collection:** Create a new field in your collection to convert saved JSON content to HTML. Payload generates and outputs the HTML for use in your frontend.
2. **Generating HTML on any server** Convert JSON to HTML on-demand on the server.
The editor comes with built-in HTML serializers, simplifying the process of converting JSON to HTML.
#### Outputting HTML from the Collection
To add HTML generation directly within the collection, follow the example below:
```ts
import type { CollectionConfig } from 'payload/types'
import {
HTMLConverterFeature,
lexicalEditor,
lexicalHTML
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
// The HTMLConverter Feature is the feature which manages the HTML serializers.
// If you do not pass any arguments to it, it will use the default serializers.
HTMLConverterFeature({}),
],
}),
},
lexicalHTML('nameOfYourRichTextField', { name: 'nameOfYourRichTextField_html' }),
],
}
```
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:
If you wish to convert JSON to HTML ad-hoc, use this code snippet:
```ts
import type { SerializedEditorState } from 'lexical'
import {
type SanitizedEditorConfig,
convertLexicalToHTML,
consolidateHTMLConverters,
} from '@payloadcms/richtext-lexical'
async function lexicalToHTML(editorData: SerializedEditorState, editorConfig: SanitizedEditorConfig) {
return await convertLexicalToHTML({
converters: consolidateHTMLConverters({ editorConfig }),
data: editorData,
})
}
```
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.
#### Creating your own HTML Converter
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
```ts
import type { HTMLConverter } from '@payloadcms/richtext-lexical'
import payload from 'payload'
const UploadHTMLConverter: HTMLConverter<SerializedUploadNode> = {
converter: async ({ node }) => {
const uploadDocument = await payload.findByID({
id: node.value.id,
collection: node.relationTo,
})
const url = (payload?.config?.serverURL || '') + uploadDocument?.url
if (!(uploadDocument?.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}"/>`
},
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.
}
```
As you can see, we have access to all the information saved in the node (for the Upload node, this is `value`and `relationTo`) and we can use that to generate the HTML.
The `convertLexicalToHTML` is part of `@payloadcms/richtext-lexical` automatically handles traversing the editor state and calling the correct converter for each node.
#### Embedding the HTML Converter in your Feature
You can embed your HTML Converter directly within your custom `Feature`, allowing it to be handled automatically by the `consolidateHTMLConverters` function. Here is an example:
```ts
export const UploadFeature = (props?: UploadFeatureProps): FeatureProvider => {
return {
feature: () => {
return {
nodes: [
{
converters: {
html: yourHTMLConverter, // <= This is where you define your HTML Converter
},
node: UploadNode,
type: UploadNode.getType(),
//...
},
],
plugins: [/*...*/],
props: props,
slashMenu: {/*...*/},
}
},
key: 'upload',
}
}
```
### Headless Editor
Lexical provides a seamless way to perform conversions between various other formats:
- HTML to Lexical (or, importing HTML into the lexical editor)
- Markdown to Lexical (or, importing Markdown into the lexical editor)
- Lexical to Markdown
A headless editor can perform such conversions outside of the main editor instance. Follow this method to initiate a headless editor:
```ts
import { createHeadlessEditor } from '@lexical/headless' // <= make sure this package is installed
import {
getEnabledNodes,
sanitizeEditorConfig,
} from '@payloadcms/richtext-lexical'
const yourEditorConfig; // <= your editor config here
const headlessEditor = createHeadlessEditor({
nodes: getEnabledNodes({
editorConfig: sanitizeEditorConfig(yourEditorConfig),
}),
})
```
### Getting the editor config
As you can see, you need to provide an editor config in order to create a headless editor. This is because the editor config is used to determine which nodes & features are enabled, and which converters are used.
To get the editor config, simply import the default editor config and adjust it - just like you did inside of the `editor: lexicalEditor({})` property:
```ts
import { defaultEditorConfig, defaultEditorFeatures } from '@payloadcms/richtext-lexical' // <= make sure this package is installed
const yourEditorConfig = defaultEditorConfig
// If you made changes to the features of the field's editor config, you should also make those changes here:
yourEditorConfig.features = [
...defaultEditorFeatures,
// Add your custom features here
]
```
### HTML => Lexical
Once you have your headless editor instance, you can use it to convert HTML to Lexical:
```ts
import { $generateNodesFromDOM } from '@lexical/html'
import { $getRoot,$getSelection } from 'lexical'
import { JSDOM } from 'jsdom';
headlessEditor.update(() => {
// In a headless environment you can use a package such as JSDom to parse the HTML string.
const dom = new JSDOM(htmlString)
// Once you have the DOM instance it's easy to generate LexicalNodes.
const nodes = $generateNodesFromDOM(headlessEditor, dom.window.document)
// Select the root
$getRoot().select()
// Insert them at a selection.
const selection = $getSelection()
selection.insertNodes(nodes)
}, { discrete: true })
// Do this if you then want to get the editor JSON
const editorJSON = headlessEditor.getEditorState().toJSON()
```
Functions prefixed with a `$` can only be run inside of an `editor.update()` or `editorState.read()` callback.
This has been taken from the [lexical serialization & deserialization docs](https://lexical.dev/docs/concepts/serialization#html---lexical).
<Banner type="success">
<strong>Note:</strong>
<br />
Using the <code>discrete: true</code> flag ensures instant updates to the editor state. If immediate reading of the updated state isn't necessary, you can omit the flag.
</Banner>
### Markdown => Lexical
Convert markdown content to the Lexical editor format with the following:
```ts
import { $convertFromMarkdownString } from '@lexical/markdown'
import { sanitizeEditorConfig } from '@payloadcms/richtext-lexical'
const yourSanitizedEditorConfig = sanitizeEditorConfig(yourEditorConfig) // <= your editor config here
const markdown = `# Hello World`
headlessEditor.update(() => { $convertFromMarkdownString(markdown, yourSanitizedEditorConfig.features.markdownTransformers) }, { discrete: true })
// Do this if you then want to get the editor JSON
const editorJSON = headlessEditor.getEditorState().toJSON()
```
### Lexical => Markdown
Export content from the Lexical editor into Markdown format using these steps:
1. Import your current editor state into the headless editor.
2. Convert and fetch the resulting markdown string.
Here's the code for it:
```ts
import { $convertToMarkdownString } from '@lexical/markdown'
import { sanitizeEditorConfig } from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from "lexical"
const yourSanitizedEditorConfig = sanitizeEditorConfig(yourEditorConfig) // <= your editor config here
const yourEditorState: SerializedEditorState // <= your current editor state here
// Import editor state into your headless editor
try {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately
} catch (e) {
logger.error({ err: e }, 'ERROR parsing editor state')
}
// Export to markdown
let markdown: string
headlessEditor.getEditorState().read(() => {
markdown = $convertToMarkdownString(yourSanitizedEditorConfig?.features?.markdownTransformers)
})
```
The `.setEditorState()` function immediately updates your editor state. Thus, there's no need for the `discrete: true` flag when reading the state afterward.
### Lexical => Plain Text
Export content from the Lexical editor into plain text using these steps:
1. Import your current editor state into the headless editor.
2. Convert and fetch the resulting plain text string.
Here's the code for it:
```ts
import type { SerializedEditorState } from "lexical"
import { $getRoot } from "lexical"
const yourEditorState: SerializedEditorState // <= your current editor state here
// Import editor state into your headless editor
try {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately
} catch (e) {
logger.error({ err: e }, 'ERROR parsing editor state')
}
// Export to plain text
const plainTextContent = headlessEditor.getEditorState().read(() => {
return $getRoot().getTextContent()
}) || ''
```
## Migrating from Slate
While both Slate and Lexical save the editor state in JSON, the structure of the JSON is different.
### Migration via SlateToLexicalFeature
One way to handle this is to just give your lexical editor the ability to read the slate JSON.
Simply add the `SlateToLexicalFeature` to your editor:
```ts
import type { CollectionConfig } from 'payload/types'
import {
SlateToLexicalFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
SlateToLexicalFeature({})
],
}),
},
],
}
```
and done! Now, everytime this lexical editor is initialized, it converts the slate date to lexical on-the-fly. If the data is already in lexical format, it will just pass it through.
This is by far the easiest way to migrate from Slate to Lexical, although it does come with a few caveats:
- 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.
### 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 { YourDocumentType } from 'payload/generated-types'
import {
cloneDeep,
convertSlateToLexical,
defaultSlateConverters,
} from '@payloadcms/richtext-lexical'
import { AnotherCustomConverter } from './lexicalFeatures/converters/AnotherCustomConverter'
export async function convertAll(payload: Payload, collectionName: string, fieldName: string) {
const docs: YourDocumentType[] = await payload.db.collections[collectionName].find({}).exec() // Use MongoDB models directly to query all documents at once
console.log(`Found ${docs.length} ${collectionName} docs`)
const converters = cloneDeep([...defaultSlateConverters, AnotherCustomConverter])
// Split docs into batches of 20.
const batchSize = 20
const batches = []
for (let i = 0; i < docs.length; i += batchSize) {
batches.push(docs.slice(i, i + batchSize))
}
let processed = 0 // Number of processed docs
for (const batch of batches) {
// Process each batch asynchronously
const promises = batch.map(async (doc: YourDocumentType) => {
const richText = doc[fieldName]
if (richText && Array.isArray(richText) && !('root' in richText)) { // It's Slate data - skip already-converted data
const converted = convertSlateToLexical({
converters: converters,
slateData: richText,
})
await payload.update({
id: doc.id,
collection: collectionName as any,
data: {
[fieldName]: converted,
},
})
}
})
// Wait for all promises in the batch to complete. Resolving batches of 20 asynchronously is faster than waiting for each doc to update individually
await Promise.all(promises)
// Update the count of processed docs
processed += batch.length
console.log(`Converted ${processed} of ${docs.length}`)
}
}
```
The `convertSlateToLexical` is the same method used in the `SlateToLexicalFeature` - it handles traversing the Slate JSON for you.
Do note that this script might require adjustment depending on your document structure, especially if you have nested richText fields or localization enabled.
### Converting custom Slate nodes
If you have custom Slate nodes, create a custom converter for them. Here's the Upload converter as an example:
```ts
import type { SerializedUploadNode } from '../uploadNode.'
import type { SlateNodeConverter } from '@payloadcms/richtext-lexical'
export const SlateUploadConverter: SlateNodeConverter = {
converter({ slateNode }) {
return {
fields: {
...slateNode.fields,
},
format: '',
relationTo: slateNode.relationTo,
type: 'upload',
value: {
id: slateNode.value?.id || '',
},
version: 1,
} as const as SerializedUploadNode
},
nodeTypes: ['upload'],
}
```
It's pretty simple: You get a Slate node as input, and you return the lexical node. The `nodeTypes` array is used to determine which Slate nodes this converter can handle.
When using a migration script, you can add your custom converters to the `converters` property of the `convertSlateToLexical` props, as seen in the example above
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 {
SlateToLexicalFeature,
lexicalEditor,
defaultSlateConverters
} from '@payloadcms/richtext-lexical'
import { YourCustomConverter } from '../converters/YourCustomConverter'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
SlateToLexicalFeature({
converters: [
...defaultSlateConverters,
YourCustomConverter
]
}),
],
}),
},
],
}
```
## Migrating from payload-plugin-lexical
Migrating from [payload-plugin-lexical](https://github.com/AlessioGr/payload-plugin-lexical) works similar to migrating from Slate.
Instead of a `SlateToLexicalFeature` there is a `LexicalPluginToLexicalFeature` you can use. And instead of `convertSlateToLexical` you can use `convertLexicalPluginToLexical`.
## Coming Soon
Lots more documentation will be coming soon, which will show in detail how to create your own custom features within Lexical.
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.

View File

@@ -1,18 +0,0 @@
---
title: Overview
label: Overview
order: 10
desc: Rich Text within Payload is extremely powerful. We've combined the beauty of the Medium editor with the power of the Notion editor all in one place.
keywords: slatejs, lexical, rich text, json, custom editor, javascript, typescript
---
Payload currently supports two official rich text editors and you can choose either one depending on your needs.
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
2. [Lexical](/docs/rich-text/lexical) - beta, where things will be moving in the future
These editors are built on an "adapter pattern" which means that you will need to install the editor you'd like to use. Take a look at the docs for the editor you'd like to use for instructions on how to install it.
The big TL;DR here is that Slate is what we have used in the past, and we still support it for existing projects, but if you're building something new and you're feeling adventurous, you should give Lexical a shot. Slate has a lot of good stuff, but Lexical has lots more.
No matter which editor you use, you have to install it at the top-level on the `config.editor` property, which will then cascade throughout all of your rich text fields and be used accordingly. Additionally, you also have the option to override the editor on a field-by-field basis if you'd like.

View File

@@ -1,365 +0,0 @@
---
title: Slate Rich Text
label: Slate
order: 20
desc: The Slate editor has been supported by Payload since beta. It's very powerful and stores content as JSON, which unlocks a ton of power.
keywords: slatejs, slate, rich text, editor, headless cms
---
The Slate editor has been supported by Payload since we released our initial beta. It's battle-tested and will continue to be supported into the future.
If you are migrating a Payload project from 1.x or earlier, you can continue to use the Slate editor as long as you'd like.
To use the Slate editor, first you need to install it:
```
npm install --save @payloadcms/richtext-slate
```
After installation, you can pass it to your top-level Payload config:
```ts
import { buildConfig } from 'payload/config'
import { slateEditor } from '@payloadcms/richtext-slate'
export default buildConfig({
collections: [
// your collections here
],
// Pass the Slate editor to the root config
editor: slateEditor({})
})
```
And here's an example for how to install the Slate editor on a field-by-field basis, while customizing its options:
```ts
import { CollectionConfig } from 'payload/types'
import { slateEditor } from '@payloadcms/richtext-slate'
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'content',
type: 'richText',
// Pass the Slate editor here and configure it accordingly
editor: slateEditor({
admin: {
elements: [
// customize elements allowed in Slate editor here
],
leaves: [
// customize leaves allowed in Slate editor here
]
}
})
}
]
}
```
## Admin Options
**`elements`**
The `elements` property is used to specify which built-in or custom [SlateJS elements](https://docs.slatejs.org/concepts/02-nodes#element) should be made available to the field within the admin panel.
The default `elements` available in Payload are:
- `h1`
- `h2`
- `h3`
- `h4`
- `h5`
- `h6`
- `blockquote`
- `link`
- `ol`
- `ul`
- `textAlign`
- `indent`
- [`relationship`](#relationship-element)
- [`upload`](#upload-element)
- [`textAlign`](#text-align)
**`leaves`**
The `leaves` property specifies built-in or custom [SlateJS leaves](https://docs.slatejs.org/concepts/08-rendering#leaves) to be enabled within the Admin panel.
The default `leaves` available in Payload are:
- `bold`
- `code`
- `italic`
- `strikethrough`
- `underline`
**`link.fields`**
This allows [fields](/docs/fields/overview) to be saved as extra fields on a link inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the link element.
`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).
![RichText link fields](https://payloadcms.com/images/docs/fields/richText/rte-link-fields-modal.jpg)
_RichText link with custom fields_
**`upload.collections[collection-name].fields`**
This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.
![RichText upload element](https://payloadcms.com/images/docs/fields/richText/rte-upload-element.jpg)
_RichText field using the upload element_
![RichText upload element modal](https://payloadcms.com/images/docs/fields/richText/rte-upload-fields-modal.jpg)
_RichText upload element modal displaying fields from the config_
### Relationship element
The built-in `relationship` element is a powerful way to reference other Documents directly within your Rich Text editor.
### Upload element
Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.
<Banner type="success">
<strong>Tip:</strong>
<br />
Collections are automatically allowed to be selected within the Rich Text relationship and upload
elements by default. If you want to disable a collection from being able to be referenced in Rich
Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and{' '}
<strong>enableRichTextRelationship</strong> to false.
</Banner>
Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.
### TextAlign element
Text Alignment is not included by default and can be added to a Rich Text Editor by adding `textAlign` to the list of elements. TextAlign will alter the existing element to include a new `textAlign` field in the resulting JSON. This field can be used in combination with other elements and leaves to position content to the left, center or right.
### Specifying which elements and leaves to allow
To specify which default elements or leaves should be allowed to be used for this field, define arrays that contain string names for each element or leaf you wish to enable. To specify a custom element or leaf, pass an object with all corresponding properties as outlined below. View the [example](#example) to reference how this all works.
### Building custom elements and leaves
You can design and build your own Slate elements and leaves to extend the editor with your own functionality. To do so, first start by reading the [SlateJS documentation](https://docs.slatejs.org/) and looking at the [Slate examples](https://www.slatejs.org/examples/richtext) to familiarize yourself with the SlateJS editor as a whole.
Once you're up to speed with the general concepts involved, you can pass in your own elements and leaves to your field's admin config.
**Both custom elements and leaves are defined via the following config:**
| Property | Description |
| --------------- | ---------------------------------------------------------- |
| **`name`** \* | The default name to be used as a `type` for this element. |
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
| **`type`** | A type that overrides the default type used by `name` |
Custom `Element`s also require the `Element` property set to a React component to be rendered as the `Element` within the rich text editor itself.
Custom `Leaf` objects follow a similar pattern but require you to define the `Leaf` property instead.
Specifying custom `Type`s let you extend your custom elements by adding additional fields to your JSON object.
### Example
`collections/ExampleCollection.ts`
```ts
import type { CollectionConfig } from 'payload/types'
import { slateEditor } from '@payloadcms/richtext-slate'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'content', // required
type: 'richText', // required
defaultValue: [
{
children: [{ text: 'Here is some default content for this field' }],
},
],
required: true,
editor: slateEditor({
admin: {
elements: [
'h2',
'h3',
'h4',
'link',
'blockquote',
{
name: 'cta',
Button: CustomCallToActionButton,
Element: CustomCallToActionElement,
plugins: [
// any plugins that are required by this element go here
],
},
],
leaves: [
'bold',
'italic',
{
name: 'highlight',
Button: CustomHighlightButton,
Leaf: CustomHighlightLeaf,
plugins: [
// any plugins that are required by this leaf go here
],
},
],
link: {
// Inject your own fields into the Link element
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: ['noopener', 'noreferrer', 'nofollow'],
},
],
},
upload: {
collections: {
media: {
fields: [
// any fields that you would like to save
// on an upload element in the `media` collection
],
},
},
},
},
}),
},
],
}
```
### Generating HTML
As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:
```ts
import React, { Fragment } from "react";
import escapeHTML from "escape-html";
import { Text } from "slate";
const serialize = (children) =>
children.map((node, i) => {
if (Text.isText(node)) {
let text = (
<span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
);
if (node.bold) {
text = <strong key={i}>{text}</strong>;
}
if (node.code) {
text = <code key={i}>{text}</code>;
}
if (node.italic) {
text = <em key={i}>{text}</em>;
}
if (node.text === '') {
text = <br />;
}
// Handle other leaf types here...
return <Fragment key={i}>{text}</Fragment>;
}
if (!node) {
return null;
}
switch (node.type) {
case "h1":
return <h1 key={i}>{serialize(node.children)}</h1>;
// Iterate through all headings here...
case "h6":
return <h6 key={i}>{serialize(node.children)}</h6>;
case "blockquote":
return <blockquote key={i}>{serialize(node.children)}</blockquote>;
case "ul":
return <ul key={i}>{serialize(node.children)}</ul>;
case "ol":
return <ol key={i}>{serialize(node.children)}</ol>;
case "li":
return <li key={i}>{serialize(node.children)}</li>;
case "link":
return (
<a href={escapeHTML(node.url)} key={i}>
{serialize(node.children)}
</a>
);
default:
return <p key={i}>{serialize(node.children)}</p>;
}
});
```
<Banner>
<strong>Note:</strong>
<br />
The above example is for how to render to JSX, although for plain HTML the pattern is similar.
Just remove the JSX and return HTML strings instead!
</Banner>
### Built-in SlateJS Plugins
Payload comes with a few built-in SlateJS plugins which can be extended to make developing your own elements and leaves a bit easier.
#### `shouldBreakOutOnEnter`
Payload's built-in heading elements all allow a "hard return" to "break out" of the currently active element. For example, if you hit `enter` while typing an `h1`, the `h1` will be "broken out of" and you'll be able to continue writing as the default paragraph element.
If you want to utilize this functionality within your own custom elements, you can do so by adding a custom plugin to your `element` like the following "large body" element example:
`customLargeBodyElement.js`:
```ts
import Button from './Button'
import Element from './Element'
import withLargeBody from './plugin'
export default {
name: 'large-body',
Button,
Element,
plugins: [
(incomingEditor) => {
const editor = incomingEditor
const { shouldBreakOutOnEnter } = editor
editor.shouldBreakOutOnEnter = (element) =>
element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)
return editor
},
],
}
```
Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin.
The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate function to add its own element name to the list of elements that should "break out" when the `enter` key is pressed.
### TypeScript
If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:
```ts
import type { RichTextCustomElement, RichTextCustomLeaf } from '@payloadcms/richtext-slate'
```

View File

@@ -1,25 +0,0 @@
---
title: Troubleshooting
label: Troubleshooting
order: 10
desc: Troubleshooting Common Issues in Payload
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express, troubleshooting
---
## Common Issues
### "Unauthorized, you must be logged in to make this request" when attempting to log in
This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve heck the following settings in your Payload config:
- CORS - If you are using the '\*', try to explicitly only allow certain domains instead including the one you have specified.
- CSRF - Do you have this set? if so, make sure your domain is whitelisted within the csrf domains. If not, probably not the issue, but probably can't hurt to whitelist it anyway.
- Cookie settings. If these are completely undefined, then that's fine. but if you have cookie domain set, or anything similar, make sure you don't have the domain misconfigured
This error likely means that the auth cookie that Payload sets after logging in successfully is being rejected because of misconfiguration.
To further investigate the issue:
- Go to the login screen. Open your inspector. Go to the Network tab.
- Log in and then find the login request that should appear in your network panel. Click the login request.
- The login request should have a Set-Cookie header on the response, and the cookie should be getting set successfully. If it is not, most browsers generally have a little yellow ⚠️ symbol that you can hover over to see why the cookie was rejected.

View File

@@ -1,197 +0,0 @@
---
title: Generating TypeScript Interfaces
label: Generating Types
order: 20
desc: Generate your own TypeScript interfaces based on your collections and globals.
keywords: headless cms, typescript, documentation, Content Management System, cms, headless, javascript, node, react, express
---
While building your own custom functionality into Payload, like plugins, hooks, access control functions, custom views, GraphQL queries / mutations, or anything else, you may benefit from generating your own TypeScript types dynamically from your Payload config itself.
### Types generation script
Run the following command in a Payload project to generate types based on your Payload config:
```
payload generate:types
```
You can run this command whenever you need to regenerate your types, and then you can use these types in your Payload code directly.
### Disable declare statement
By default, `generate:types` will add a `declare` statement to your types file, which automatically enables type inference within Payload.
If you are using your `payload-types.ts` file in other repos, though, it might be better to disable this `declare` statement, so that you don't get any TS errors in projects that use your Payload types, but do not have Payload installed.
```ts
// payload.config.ts
{
// ...
typescript: {
declare: false, // defaults to true if not set
},
}
```
If you do disable the `declare` pattern, you'll need to manually add a `declare` statement to your code in order for Payload types to be recognized. Here's an example showing how to declare your types in your `payload.config.ts` file:
```ts
import { Config } from './payload-types'
declare module 'payload' {
export interface GeneratedTypes extends Config {}
}
```
### Custom output file path
You can specify where you want your types to be generated by adding a property to your Payload config:
```ts
// payload.config.ts
{
// ...
typescript: {
// defaults to: path.resolve(__dirname, './payload-types.ts')
outputFile: path.resolve(__dirname, './generated-types.ts'),
},
}
```
The above example places your types next to your Payload config itself as the file `generated-types.ts`.
### Example Usage
For example, let's look at the following simple Payload config:
```ts
const config: Config = {
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
admin: {
user: 'users',
}
collections: [
{
slug: 'users',
fields: [
{
name: 'name',
type: 'text',
required: true,
}
]
},
{
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
]
}
]
}
```
By generating types, we'll end up with a file containing the following two TypeScript interfaces:
```ts
export interface User {
id: string
name: string
email?: string
resetPasswordToken?: string
resetPasswordExpiration?: string
loginAttempts?: number
lockUntil?: string
}
export interface Post {
id: string
title?: string
author?: string | User
}
```
### Custom Field Interfaces
For `array`, `block`, `group` and named `tab` fields, you can generate top level reusable interfaces. The following group field config:
```ts
{
type: 'group',
name: 'meta',
interfaceName: 'SharedMeta', <-- here!!
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'text',
},
],
}
```
will generate:
```ts
// a top level reusable interface!!
export interface SharedMeta {
title?: string
description?: string
}
// example usage inside collection interface
export interface Collection1 {
// ...other fields
meta?: SharedMeta
}
```
<Banner type="warning">
<strong>Naming Collisions</strong>
<br />
Since these types are hoisted to the top level, you need to be aware that naming collisions can
occur. For example, if you have a collection with the name of `Meta` and you also create a
interface with the name `Meta` they will collide. It is recommended to scope your interfaces by
appending the field type to the end, i.e. `MetaGroup` or similar.
</Banner>
### Using your types
Now that your types have been generated, payloads local API will now be typed. It is common for users to want to use this in their frontend code, we recommend generating them with payload and then copying the file over to your frontend codebase. This is the simplest way to get your types into your frontend codebase.
#### Adding an NPM script
<Banner type="warning">
<strong>Important</strong>
<br />
Payload needs to be able to find your config to generate your types.
</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.
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:
```
{
"scripts": {
"generate:types": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
},
}
```
Now you can run `yarn generate:types` to easily generate your types.

View File

@@ -1,36 +0,0 @@
---
title: TypeScript - Overview
label: Overview
order: 10
desc: Payload is the most powerful TypeScript headless CMS available.
keywords: headless cms, typescript, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload supports TypeScript natively, and not only that, the entirety of the CMS is built with TypeScript. To get started developing with Payload and TypeScript, you can use one of Payload's built-in boilerplates in one line via `create-payload-app`:
```
npx create-payload-app@latest
```
Pick a TypeScript project type to get started easily.
#### Setting up from Scratch
It's also possible to set up a TypeScript project from scratch. We plan to write up a guide for exactly how—so keep an eye out for that, too.
## Using Payload's Exported Types
Payload exports a number of types that you may find useful while writing your own plugins, hooks, access control functions, custom views, GraphQL queries / mutations, or anything else.
##### Config Types
- [Base config](/docs/configuration/overview#typescript)
- [Collections](/docs/configuration/collections#typescript)
- [Globals](/docs/configuration/globals#typescript)
- [Fields](/docs/fields/overview#typescript)
##### Hook Types
- [Collection hooks](/docs/hooks/collections#typescript)
- [Global hooks](/docs/hooks/globals#typescript)
- [Field hooks](/docs/hooks/fields#typescript)

View File

@@ -1,283 +0,0 @@
---
title: Uploads
label: Overview
order: 10
desc: Payload supports uploads, storage and management of files directly on your server, combined with powerful file access control.
keywords: uploads, images, media, overview, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
Payload provides for everything you need to enable file upload, storage, and management directly
on your server—including extremely powerful file access control.
</Banner>
![Upload admin panel functionality](https://payloadcms.com/images/docs/upload-admin.jpg)
_Admin panel screenshot depicting a Media Collection with Upload enabled_
**Here are some common use cases of Uploads:**
- Creating a "Media Library" that contains images for use throughout your site or app
- Building a Gated Content library where users need to sign up to gain access to downloadable assets like ebook PDFs, whitepapers, etc.
- Storing publicly available, downloadable assets like software, ZIP files, MP4s, etc.
**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 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
### Enabling Uploads
Every Payload Collection can opt-in to supporting Uploads by specifying the `upload` property on the Collection's config to either `true` or to an object containing `upload` options.
<Banner type="success">
<strong>Tip:</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) |
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config). |
| **`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 Express 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) |
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
| **`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. |
| **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. |
_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: [
{
name: 'thumbnail',
width: 400,
height: 300,
position: 'centre',
},
{
name: 'card',
width: 768,
height: 1024,
position: 'centre',
},
{
name: 'tablet',
width: 1024,
// By specifying `undefined` or leaving a height undefined,
// the image will be sized to a certain width,
// but it will retain its original aspect ratio
// and calculate a height automatically.
height: undefined,
position: 'centre',
},
],
adminThumbnail: 'thumbnail',
mimeTypes: ['image/*'],
},
fields: [
{
name: 'alt',
type: 'text',
},
],
}
```
### Payload-wide 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.
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:
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
collections: [
{
slug: 'media',
fields: [
{
name: 'alt',
type: 'text',
},
],
upload: true,
},
],
upload: {
limits: {
fileSize: 5000000, // 5MB, written in bytes
},
},
})
```
### Image Sizes
If you specify an array of `imageSizes` to your `upload` config, Payload will automatically crop and resize your uploads to fit each of the sizes specified by your config.
The Payload Admin panel will also automatically display all available files, including width, height, and filesize, for each of your uploaded files.
Behind the scenes, Payload relies on [`sharp`](https://sharp.pixelplumbing.com/api-resize#resize) to perform its image resizing. You can specify additional options for `sharp` to use while resizing your images.
##### Accessing the resized images in hooks
All auto-resized images are exposed to be re-used in hooks and similar via an object that is bound to `req.payloadUploadSizes`.
The object will have keys for each size generated, and each key will be set equal to a buffer containing the file data.
##### Handling Image Enlargement
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
<Banner type="error">
<strong>Note:</strong>
<br />
By default, the image size will return NULL when the uploaded image is smaller than the defined image size.
Use the `withoutEnlargement` prop to change this.
</Banner>
#### Custom file name per size
Each image size supports a `generateImageName` function that can be used to generate a custom file name for the resized image.
This function receives the original file name, the resize name, the extension, height and width as arguments.
```ts
{
name: 'thumbnail',
width: 400,
height: 300,
generateImageName: ({ height, sizeName, extension, width }) => {
return `custom-${sizeName}-${height}-${width}.${extension}`
},
}
```
### Crop and Focal Point Selector
This feature is only available for image file types.
Setting `crop: false` and `focalPoint: false` in your Upload config will be disable the respective selector in the Admin panel.
Image cropping occurs before any resizing, the resized images will therefore be generated from the cropped image (**not** the original image).
If no resizing options are specified (`imageSizes` or `resizeOptions`), the focal point selector will not be displayed.
### Disabling Local Upload Storage
If you are using a plugin to send your files off to a third-party file storage host or CDN, like Amazon S3 or similar, you may not want to store your files locally at all. You can prevent Payload from writing files to disk by specifying `disableLocalStorage: true` on your collection's upload config.
<Banner type="warning">
<strong>Note:</strong>
<br />
This is a fairly advanced feature. If you do disable local file storage, by default, your admin
panel's thumbnails will be broken as you will not have stored a file. It will be totally up to you
to use either a plugin or your own hooks to store your files in a permanent manner, as well as
provide your own admin thumbnail using <strong>upload.adminThumbnail</strong>.
</Banner>
### Admin Thumbnails
You can specify how Payload retrieves admin thumbnails for your upload-enabled Collections. This property accepts two different configurations:
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:**
```ts
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}`,
// highlight-end
},
}
```
<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>
### MimeTypes
Specifying the `mimeTypes` property can restrict what files are allowed from the user's file picker. This accepts an array of strings, which can be any valid mimetype or mimetype wildcards
Some example values are: `image/*`, `audio/*`, `video/*`, `image/png`, `application/pdf`
**Example mimeTypes usage:**
```ts
import { CollectionConfig } from 'payload/types'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticURL: '/media',
staticDir: 'media',
mimeTypes: ['image/*', 'application/pdf'], // highlight-line
},
}
```
### Uploading Files
<Banner type="warning">
<strong>Important:</strong>
<br />
Uploading files is currently only possible through the REST and Local APIs due to how GraphQL
works. It's difficult and fairly nonsensical to support uploading files through GraphQL.
</Banner>
To upload a file, use your collection's [`create`](/docs/rest-api/overview#collections) endpoint. Send it all the data that your Collection requires, as well as a `file` key containing the file that you'd like to upload.
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.
### Access Control
All files that are uploaded to each Collection automatically support the `read` [Access Control](/docs/access-control/overview) function from the Collection itself. You can use this to control who should be allowed to see your uploads, and who should not.

View File

@@ -1,75 +0,0 @@
---
title: Autosave
label: Autosave
order: 30
desc: Using Payload's Draft functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready.
keywords: version history, revisions, audit log, draft, publish, autosave, Content Management System, cms, headless, javascript, node, react, expresss
---
Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready. The Admin UI will automatically adapt to autosaving progress at an interval that you define, and will store all autosaved changes as a new Draft version. Never lose your work - and publish changes to the live document only when you're ready.
<Banner type="warning">
Autosave relies on Versions and Drafts being enabled in order to function.
</Banner>
![Autosave Enabled](/images/docs/autosave-enabled.png)
_If Autosave is enabled, drafts will be created automatically as the document is modified and the Admin UI adds an indicator describing when the document was last saved to the top right of the sidebar._
### Options
Collections and Globals both support the same options for configuring autosave. You can either set `versions.drafts.autosave` to `true`, or pass an object to configure autosave properties.
| Drafts Autosave Options | Description |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interval` | Define an `interval` in milliseconds to automatically save progress while documents are edited. Document updates are "debounced" at this interval. Defaults to `800`. |
**Example config with versions, drafts, and autosave enabled:**
```ts
import { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {
// If there is a user logged in,
// let them retrieve all documents
if (req.user) return true
// If there is no user,
// restrict the documents that are returned
// to only those where `_status` is equal to `published`
return {
_status: {
equals: 'published',
},
}
},
},
versions: {
drafts: {
autosave: true,
// Alternatively, you can specify an `interval`:
// autosave: {
// interval: 1500,
// },
},
},
//.. the rest of the Pages config here
}
```
### Autosave API
When `autosave` is enabled, all `update` operations within Payload expose a new argument called `autosave`. When set to `true`, Payload will treat the incoming draft update as an `autosave`. This is primarily used by the Admin UI, but there may be some cases where you are building an app for your users and wish to implement `autosave` in your own app. To do so, use the `autosave` argument in your `update` operations.
#### How autosaves are stored
If we created a new version for each autosave, you'd quickly find a ton of autosaves that clutter up your `_versions` collection within the database. That would be messy quick because `autosave` is typically set to save a document at ~800ms intervals.
<Banner type="success">
Instead of creating a new version each time a document is autosaved, Payload smartly only creates{' '}
<strong>one</strong> autosave version, and then updates that specific version with each autosave
performed. This makes sure that your versions remain nice and tidy.
</Banner>

View File

@@ -1,175 +0,0 @@
---
title: Drafts
label: Drafts
order: 20
desc: Enable drafts on collection documents or globals and build true preview environments for your data.
keywords: version history, drafts, preview, draft, restore, publish, autosave, Content Management System, cms, headless, javascript, node, react, express
---
Payload's Draft functionality builds on top of the Versions functionality to allow you to make changes to your collection documents and globals, but publish only when you're ready. This functionality allows you to build powerful Preview environments for your data, where you can make sure your changes look good before publishing documents.
<Banner type="warning">Drafts rely on Versions being enabled in order to function.</Banner>
By enabling Versions with Drafts, your collections and globals can maintain _newer_, and _unpublished_ versions of your documents. It's perfect for cases where you might want to work on a document, update it and save your progress, but not necessarily make it publicly published right away. Drafts are extremely helpful when building preview implementations.
![Drafts Enabled](/images/docs/drafts-enabled.png)
_If Drafts are enabled, the typical Save button is replaced with new actions which allow you to either save a draft, or publish your changes._
### Options
Collections and Globals both support the same options for configuring drafts. You can either set `versions.drafts` to `true`, or pass an object to configure draft properties.
| Draft Option | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autosave` | Enable `autosave` to automatically save progress while documents are edited. To enable, set to `true` or pass an object with [options](/docs/versions/autosave). |
| `validate` | Set `validate` to `true` to validate draft documents when saved. Default is `false`. |
### Database changes
By enabling drafts on a collection or a global, Payload will <strong>automatically inject a new field into your schema</strong> called `_status`. The `_status` field is used internally by Payload to store if a document is set to `draft` or `published`.
**Admin UI status indication**
Within the Admin UI, if drafts are enabled, a document can be shown with one of three "statuses":
1. <strong>Draft</strong> - if a document has never been published, and only draft versions of the document
are present
1. <strong>Published</strong> - if a document is published and there are no newer drafts available
1. <strong>Changed</strong> - if a document has been published, but there are newer drafts available
and not yet published
### Draft API
<Banner type="success">
If drafts are enabled on your collection or global, important and powerful changes are made to
your REST, GraphQL, and Local APIs that allow you to specify if you are interacting with drafts or
with live documents.
</Banner>
##### Updating or creating drafts
If you enable drafts on a collection or global, the `create` and `update` operations for REST, GraphQL, and Local APIs expose a new option called `draft` which allows you to specify if you are creating or updating a <strong>draft</strong>, or if you're just sending your changes straight to the published document. For example, if you pass the query parameter `?draft=true` to a REST `create` or `update` operation, your action will be treated as if you are creating a `draft` and not a published document. By default, the `draft` argument is set to `false`.
**Required fields**
If `draft` is enabled while creating or updating a document, all fields are considered as not required, so that you can save drafts that are incomplete.
##### Reading drafts vs. published documents
In addition to the `draft` argument within `create` and `update` operations, a `draft` argument is also exposed for `find` and `findByID` operations.
If `draft` is set to `true` while reading a document, <strong>Payload will automatically replace returned document(s) with their newest drafts</strong> if any newer drafts are available.
<strong>For example, let's take the following scenario:</strong>
1. You create a new collection document and publish it right away
1. You then make some updates, and save the updates as a draft
1. You then make some further updates, and save more updates as another draft
Here, you will have a published document that resides in your main collection, and then you'll have two _newer_ drafts that reside in the `_[collectionSlug]_versions` database collection.
If you simply fetch your created document using a `find` or `findByID` operation, your published document will be returned and the drafts will be ignored.
But, if you specify `draft` as `true`, Payload will automatically replace your published document's content with content coming from the most recently saved `version`. In this case, as we have created _two_ versions in the above scenario, Payload will send back data from the newest (second) draft and your document will appear as the most recently drafted version instead of the published version.
### Controlling who can see Collection drafts
<Banner type="warning">
If you're using the <strong>drafts</strong> feature, it's important for you to consider who can
view your drafts, and who can view only published documents. Luckily, Payload makes this extremely
simple and puts the power completely in your hands.
</Banner>
##### Restricting draft access
You can use the `read` [Access Control](/docs/access-control/collections#read) method to restrict who is able to view drafts of your documents by simply returning a [query constraint](/docs/queries/overview) which restricts the documents that any given user is able to retrieve.
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'
export const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {
// If there is a user logged in,
// let them retrieve all documents
if (req.user) return true
// If there is no user,
// restrict the documents that are returned
// to only those where `_status` is equal to `published`
return {
_status: {
equals: 'published',
},
}
},
},
versions: {
drafts: true,
},
//.. the rest of the Pages config here
}
```
<Banner type="warning">
<strong>Note regarding adding versions to an existing collection</strong>
<br />
If you already have a collection with documents, and you <em>opt in</em> to draft functionality
after you have already created existing documents, all of your old documents{' '}
<em>will not have a _status field</em> until you resave them. For this reason, if you are{' '}
<em>adding</em> versions into an existing collection, you might want to write your access control
function to allow for users to read both documents where{' '}
<strong>_status is equal to "published"</strong> as well as where{' '}
<strong>_status does not exist</strong>.
</Banner>
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'
export const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {
// If there is a user logged in,
// let them retrieve all documents
if (req.user) return true
// If there is no user,
// restrict the documents that are returned
// to only those where `_status` is equal to `published`
// or where `_status` does not exist
return {
or: [
{
_status: {
equals: 'published',
},
},
{
_status: {
exists: false,
},
},
],
}
},
},
versions: {
drafts: true,
},
//.. the rest of the Pages config here
}
```
### Unpublishing drafts
If a document is published, the Payload Admin UI will be updated to show an "unpublish" button at the top of the sidebar, which will "unpublish" the currently published document. Consider this as a way to "revert" a document back to a draft state. On the API side, this is done by simply setting `_status: 'draft'` on any document.
### Reverting to published
If a document is published, and you have made further changes which are saved as a draft, Payload will show a "revert to published" button at the top of the sidebar which will allow you to reject your draft changes and "revert" back to the published state of the document. Your drafts will still be saved, but a new version will be created that will reflect the last published state of the document.

View File

@@ -1,254 +0,0 @@
---
title: Versions
label: Overview
order: 10
desc: Keep a version history or audit log of changes and publish collection documents and globals.
keywords: version history, revisions, audit log, draft, publish, restore, autosave, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
Payload's powerful Versions functionality allows you to keep a running history of changes over
time and extensible to fit any content publishing workflow.
</Banner>
When enabled, Payload will automatically scaffold a new Collection in your database to store versions of your document(s) over time, and the Admin UI will be extended with additional views that allow you to browse document versions, view diffs in order to see exactly what has changed in your documents (and when they changed), and restore documents back to prior versions easily.
![Versions](/images/docs/versions.png)
_Comparing an old version to a newer version of a document_
**With Versions, you can:**
- Maintain an audit log / history of every change ever made to a document, including monitoring for what user made which change
- Restore documents and globals to prior states in case you need to roll back changes
- Build a true [Draft Preview](/docs/versions/drafts) mode for your data
- Manage who can see Drafts, and who can only see Published documents via [Access Control](/docs/access-control/overview)
- Enable [Autosave](/docs/versions/autosave) on collections and globals to never lose your work again
- Build a powerful publishing schedule mechanism to create documents and have them become publicly readable automatically at a future date
<Banner type="success">
Versions are extremely performant and totally opt-in. They don't change the shape of your data at
all. All versions are stored in a separate Collection and can be turned on and off easily at your
discretion.
</Banner>
### Options
Versions support a few different levels of functionality that each come with their own impacts to document workflow.
##### Versions enabled, drafts disabled
If you enable versions but keep draft mode disabled, Payload will simply create a new version of a document each time you update a document. This is great for use cases where you need to retain a history of all document updates over time, but always want to treat the newest document version as the version that is "published".
For example, a use case for "versions enabled, drafts disabled" could be on a collection of users, where you might want to keep a version history (or audit log) of all changes ever made to users - but any changes to users should _always_ be treated as "published" and you have no need to maintain a "draft" version of a user.
##### Versions and drafts enabled
If you have versions _and_ drafts enabled, you are able to control which documents are published, and which are considered draft. That lets you write [access control](/docs/access-control/overview) to control who can see published documents, and who can see draft documents. It also lets you save versions (drafts) that are _newer_ than your most recently published document, which is helpful if you want to draft changes and maybe even preview them before you publish the changes. Read more about Drafts [here](/docs/versions/drafts).
##### Versions, drafts, and autosave enabled
When you have versions, drafts, _and_ `autosave` enabled, the Admin UI will automatically save changes that you make to a new `draft` version as you edit a document, which makes sure that you never lose your changes ever again. Autosave will not affect your published post at all—instead, it'll just save your changes and let you publish them whenever you or your editors are ready to do so. Read more about Autosave [here](/docs/versions/autosave).
### Collection config
Configuring Versions is done by adding the `versions` key to your Collection configs. Set it to `true` to enable default Versions settings, or customize versions options by setting the property equal to an object containing the following available options:
| Option | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maxPerDoc` | Use this setting to control how many versions to keep on a document by document basis. Must be an integer. Defaults to 100, use 0 to save all versions. |
| `drafts ` | Enable [Drafts](/docs/versions/drafts) mode for this collection. To enable, set to `true` or pass an object with `draft` [options](/docs/versions/drafts#options). |
### Global config
Global versions work similarly to Collection versions but have a slightly different set of config properties supported.
| Option | Description |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max` | Use this setting to control how many versions to keep on a global by global basis. Must be an integer. |
| `drafts` | Enable [Drafts](/docs/versions/drafts) mode for this global. To enable, set to `true` or pass an object with `draft` [options](/docs/versions/drafts#options) |
#### Database impact
By enabling `versions`, a new database collection will be made to store versions for your collection or global. The collection will be named based off the `slug` of the collection or global and will follow this pattern (where `slug` is replaced with the `slug` of your collection or global):
```
_slug_versions
```
Each document in this new `versions` collection will store a set of meta properties about the version as well as a _full_ copy of the document. For example, a version's data might look like this for a Collection document:
```json
{
"_id": "61cf752c19cdf1b1af7b61f1", // a unique ID of this version
"parent": "61ce1354091d5b3ffc20ea6e", // the ID of the parent document
"autosave": false, // used to denote if this version was created via autosave
"version": {
// your document's data goes here
// all fields are set to not required and this property can be partially complete
},
"createdAt": "2021-12-31T21:25:00.992+00:00",
"updatedAt": "2021-12-31T21:25:00.992+00:00"
}
```
Global versions are stored the same as the collection version shown above, except they do not feature the `parent` property, as each Global receives its own `versions` collection. That means we know that all versions in that collection correspond to that specific global.
### Version operations
Versions expose new operations for both collections and globals. They allow you to find and query versions, find a single version by ID, and publish (or restore) a version by ID. Both Collections and Globals support the same new operations. They are used primarily by the admin UI, but if you are writing custom logic in your app and would like to utilize them, they're available for you to use as well via REST, GraphQL, and Local APIs.
**Collection REST endpoints:**
| Method | Path | Description |
| ------ | ------------------------------------ | --------------------------------- |
| `GET` | `/api/{collectionSlug}/versions` | Find and query paginated versions |
| `GET` | `/api/{collectionSlug}/versions/:id` | Find a specific version by ID |
| `POST` | `/api/{collectionSlug}/versions/:id` | Restore a version by ID |
**Collection GraphQL queries:**
| Query Name | Operation |
| ---------------------------------------- | ----------------- |
| **`version{collection.label.singular}`** | `findVersionByID` |
| **`versions{collection.label.plural}`** | `findVersions` |
**And mutation:**
| Query Name | Operation |
| ----------------------------------------------- | ---------------- |
| **`restoreVersion{collection.label.singular}`** | `restoreVersion` |
**Collection Local API methods:**
#### Find
```js
// Result will be a paginated set of Versions.
// See /docs/queries/pagination for more.
const result = await payload.findVersions({
collection: 'posts', // required
depth: 2,
page: 1,
limit: 10,
where: {}, // pass a `where` query here
sort: '-createdAt',
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Find by ID
```js
// Result will be a Post document.
const result = await payload.findVersionByID({
collection: 'posts', // required
id: '507f1f77bcf86cd799439013', // required
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Restore
```js
// Result will be the restored global document.
const result = await payload.restoreVersion({
collection: 'posts', // required
id: '507f1f77bcf86cd799439013', // required
depth: 2,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
**Global REST endpoints:**
| Method | Path | Description |
| ------ | ---------------------------------------- | --------------------------------- |
| `GET` | `/api/globals/{globalSlug}/versions` | Find and query paginated versions |
| `GET` | `/api/globals/{globalSlug}/versions/:id` | Find a specific version by ID |
| `POST` | `/api/globals/{globalSlug}/versions/:id` | Restore a version by ID |
**Global GraphQL queries:**
| Query Name | Operation |
| ---------------------------- | ----------------- |
| **`version{global.label}`** | `findVersionByID` |
| **`versions{global.label}`** | `findVersions` |
**Global GraphQL mutation:**
| Query Name | Operation |
| ---------------------------------- | ---------------- |
| **`restoreVersion{global.label}`** | `restoreVersion` |
**Global Local API methods:**
#### Find
```js
// Result will be a paginated set of Versions.
// See /docs/queries/pagination for more.
const result = await payload.findGlobalVersions({
slug: 'header', // required
depth: 2,
page: 1,
limit: 10,
where: {}, // pass a `where` query here
sort: '-createdAt',
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Find by ID
```js
// Result will be a Post document.
const result = await payload.findGlobalVersionByID({
slug: 'header', // required
id: '507f1f77bcf86cd799439013', // required
depth: 2,
locale: 'en',
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
#### Restore
```js
// Result will be the restored global document.
const result = await payload.restoreGlobalVersion({
slug: 'header', // required
id: '507f1f77bcf86cd799439013', // required
depth: 2,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
})
```
### Access Control
Versions expose a new access control function on both Collections and Globals that allow for you to control who can see versions of documents, and who can't.
**New version access control:**
| Function | Allows/Denies Access |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| **`readVersions`** | Used to control who can read versions, and who can't. Will automatically restrict the Admin UI version viewing access. |

View File

@@ -77,7 +77,12 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
}, [action, submit])
return (
<FormSubmit buttonStyle="secondary" className={`${baseClass}__draft`} disabled={disabled} onClick={save}>
<FormSubmit
buttonStyle="secondary"
className={`${baseClass}__draft`}
disabled={disabled}
onClick={save}
>
{t('saveDraft')}
</FormSubmit>
)

View File

@@ -130,6 +130,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
const query = await payload.db.queryDrafts<GeneratedTypes['collections'][TSlug]>({
collection: collectionConfig.slug,
limit: 0,
locale,
req,
where: versionsWhere,

View File

@@ -405,7 +405,7 @@ function buildObjectType({
}
await Promise.all(resultPromises)
return results
return results.filter((doc) => doc != null)
}
let id = value

View File

@@ -21,6 +21,7 @@ import ro from './ro.json'
import rs from './rs.json'
import rsLatin from './rs-latin.json'
import ru from './ru.json'
import sl from './sl.json'
import sv from './sv.json'
import th from './th.json'
import tr from './tr.json'
@@ -53,6 +54,7 @@ export default {
rs,
rsLatin,
ru,
sl,
sv,
th,
tr,

Some files were not shown because too many files have changed in this diff Show More