# Payload Documentation
# What is Payload?
Source: https://payloadcms.com/docs/getting-started/what-is-payload
**Payload is the Next.js fullstack framework.** Write a Payload Config and instantly get:
- A full Admin Panel using React server / client components, matching the shape of your data and completely extensible with your own React components
- Automatic database schema, including direct DB access and ownership, with migrations, transactions, proper indexing, and more
- Instant REST, GraphQL, and straight-to-DB Node.js APIs
- Authentication which can be used in your own apps
- A deeply customizable access control pattern
- File storage and image management tools like cropping / focal point selection
- Live preview - see your frontend render content changes in realtime as you update
- Lots more
### Instant backend superpowers
No matter what you're building, Payload will give you backend superpowers. Your entire Payload config can be installed in one line into any existing Next.js app, and is designed to catapult your development process. Payload takes the most complex and time-consuming parts of any modern web app and makes them simple.
### Open source - deploy anywhere, including Vercel
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node.js app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js application.
### Code-first and version controlled
In Payload, there are no "click ops" - as in clicking around in an Admin Panel to define your schema. In Payload, everything is done the right way—code-first and version controlled like a proper backend. But once developers define how Payload should work, non-technical users can independently make use of its Admin Panel to manage whatever they need to without having to know code whatsoever.
### Fully extensible
Even in spite of how much you get out of the box, you still have full control over every aspect of your app - be it database, admin UI, or anything else. Every part of Payload has been designed to be extensible and customizable with modern TypeScript / React. And you'll fully understand the code that you write.
## Use Cases
Payload started as a headless Content Management System (CMS), but since, we've seen our community leverage Payload in ways far outside of simply managing pages and blog posts. It's grown into a full-stack TypeScript app framework.
Large enterprises use Payload to power significant internal tools, retailers power their entire storefronts without the need for headless Shopify, and massive amounts of digital assets are stored + managed within Payload. Of course, websites large and small still use Payload for content management as well.
### Headless CMS
The biggest barrier in large web projects cited by marketers is engineering. On the flip side, engineers say the opposite. This is a big problem that has yet to be solved even though we have countless CMS options.
Payload has restored a little love back into the dev / marketer equation with features like Live Preview, redirects, form builders, visual editing, static A/B testing, and more. But even with all this focus on marketing efficiency, we aren't compromising on the developer experience. That way engineers and marketers alike can be proud of the products they build.
If you're building a website and your frontend is on Next.js, then Payload is a no-brainer.
Instead of going out and signing up for a SaaS vendor that makes it so you
have to manage two completely separate concerns, with little to no native
connection back and forth, just install Payload in your existing Next.js repo
and instantly get a full CMS.
Get started with Payload as a CMS using our official Website template:
```
npx create-payload-app@latest -t website
```
### Enterprise Tool
When a large organization starts up a new software initiative, there's a lot of plumbing to take care of.
- Scaffold the data layer with an ORM or an app framework like Ruby on Rails or Laravel
- Implement their SSO provider for authentication
- Design an access control pattern for authorization
- Open up any REST endpoints required or implement GraphQL queries / mutations
- Implement a migrations workflow for the database as it changes over time
- Integrate with other third party solutions by crafting a system of webhooks or similar
And then there's the [Admin Panel](../admin/overview). Most enterprise tools require an admin UI, and building one from scratch can be the most time-consuming aspect of any new enterprise tool. There are off-the-shelf packages for app frameworks like Rails, but often the customization is so involved that using Material UI or similar from scratch might be better.
Then there are no-code admin builders that could be used. However, wiring up access control and the connection to the data layer, with proper version control, makes this a challenging task as well.
That's where Payload comes in. Payload instantly provides all of this out of the box, making complex internal tools extremely simple to both spin up and maintain over time. The only custom code that will need to be written is any custom business logic. That means Payload can expedite timelines, keep budgets low, and allow engineers to focus on their specific requirements rather than complex backend / admin UI plumbing.
Generally, the best place to start for a new enterprise tool is with a blank canvas, where you can define your own functionality:
```
npx create-payload-app@latest -t blank
```
### Headless Commerce
Companies who prioritize UX generally run into frontend constraints with traditional commerce vendors. These companies will then opt for frontend frameworks like Next.js which allow them to fine-tune their user experience as much as possible—promoting conversions, personalizing experiences, and optimizing for SEO.
But the challenge with using something like Next.js for headless commerce is that in order for non-technical users to manage the storefront, you instantly need to pair a headless commerce product with a headless CMS. Then, your editors need to bounce back and forth between different admin UIs for different functionality. The code required to seamlessly glue them together on the frontend becomes overly complex.
Payload can integrate with any payment processor like Stripe and its content authoring capabilities allow it to manage every aspect of a storefront—all in one place.
If you can build your storefront with a single backend, and only offload things like payment processing, the code will be simpler and the editing experience will be significantly streamlined. Manage products, catalogs, page content, media, and more—all in one spot.
### Digital Asset Management
Payload's API-first tagging, sorting, and querying engine lends itself perfectly to all types of content that a CMS might ordinarily store, but these strong fundamentals also make it a formidable Digital Asset Management (DAM) tool as well.
Similarly to the Ecommerce use case above, if an organization uses a CMS for its content but a separate DAM for its digital assets, administrators of both tools will need to juggle completely different services for tasks that are closely related. Two subscriptions will need to be managed, two sets of infrastructure will need to be provisioned, and two admin UIs need to be used / learned.
Payload flattens CMS and DAM into a single tool that makes no compromises on either side. Powerful features like folder-based organization, file versioning, bulk upload, and media access control allow Payload to simultaneously function as a full Digital Asset Management platform as well as a Content Management System at the same time.
[Click here](https://payloadcms.com/use-cases/digital-asset-management) for more information on how to get started with Payload as a DAM.
## Choosing a Framework
Payload is a great choice for applications of all sizes and types, but it might not be the right choice for every project. Here are some guidelines to help you decide if Payload is the right choice for your project.
### When Payload might be for you
- If data ownership and privacy are important to you, and you don't want to allow another proprietary SaaS vendor to host and own your data
- If you're building a Next.js site that needs a CMS
- If you need to re-use your data outside of a SaaS API
- If what you're building has custom business logic requirements outside of a typical headless CMS
- You want to deploy serverless on platforms like Vercel
### When Payload might not be for you
- If you can manage your project fully with code, and don't need an admin UI
- If you are building a website that fits within the limits of a tool like Webflow or Framer
- If you already have a full database and just need to visualize the data somehow
- If you are confident that you won't need code / data ownership at any point in the future
Ready to get started? First, let's review some high-level concepts that are used in Payload.
# Payload Concepts
Source: https://payloadcms.com/docs/getting-started/concepts
Payload is based around a small and intuitive set of high-level concepts. Before starting to work with Payload, it's a good idea to familiarize yourself with these concepts in order to establish a common language and understanding when discussing Payload.
## Config
The Payload Config is central to everything that Payload does. It allows for the deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon. [More details](../configuration/overview).
## Database
Payload is database agnostic, meaning you can use any type of database behind Payload's familiar APIs through what is known as a Database Adapter. [More details](../database/overview).
## Collections
A Collection is a group of records, called Documents, that all share a common schema. Each Collection is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define. [More details](../configuration/collections).
## Globals
Globals are in many ways similar to [Collections](../configuration/collections), except they correspond to only a single Document. Each Global is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define. [More details](../configuration/globals).
## Fields
Fields are the building blocks of Payload. They define the schema of the Documents that will be stored in the [Database](../database/overview), as well as automatically generate the corresponding UI within the Admin Panel. [More details](../fields/overview).
## Hooks
Hooks allow you to execute your own side effects during specific events of the Document lifecycle, such as before read, after create, etc. [More details](../hooks/overview).
## Authentication
Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the Admin Panel, as well as your own external applications. [More details](../authentication/overview).
## Access Control
Access Control determines what a user can and cannot do with any given Document, such as read, update, etc., as well as what they can and cannot see within the Admin Panel. [More details](../access-control/overview).
## Admin Panel
Payload dynamically generates a beautiful, fully type-safe interface to manage your users and data. The Admin Panel is a React application built using the Next.js App Router. [More details](../admin/overview).
## Retrieving Data
Everything Payload does (create, read, update, delete, login, logout, etc.) is exposed to you via three APIs:
- [Local API](#local-api) - Extremely fast, direct-to-database access
- [REST API](#rest-api) - Standard HTTP endpoints for querying and mutating data
- [GraphQL](#graphql-api) - A full GraphQL API with a GraphQL Playground
**Note:** All of these APIs share the exact same query language. [More
details](../queries/overview).
### Local API
By far one of the most powerful aspects of Payload is the fact that it gives you direct-to-database access to your data through the [Local API](../local-api/overview). It's _extremely_ fast and does not incur any typical HTTP overhead—you query your database directly in Node.js.
The Local API is written in TypeScript, and so it is strongly typed and extremely nice to use. It works anywhere on the server, including custom Next.js Routes, Payload Hooks, Payload Access Control, and React Server Components.
Here's a quick example of a React Server Component fetching data using the Local API:
```tsx
import React from 'react'
import config from '@payload-config'
import { getPayload } from 'payload'
const MyServerComponent: React.FC = () => {
const payload = await getPayload({ config })
// The `findResult` here will be fully typed as `PaginatedDocs`,
// where you will have the `docs` that are returned as well as
// information about how many items are returned / are available in total / etc
const findResult = await payload.find({ collection: 'pages' })
return (
{findResult.docs.map((page) => {
// Render whatever here!
// The `page` is fully typed as your Pages collection!
})}
)
}
```
For more information about the Local API, [click here](../local-api/overview).
### REST API
By default, the Payload [REST API](../rest-api/overview) is mounted automatically for you at the `/api` path of your app.
For example, if you have a Collection called `pages`:
```ts
fetch('https://localhost:3000/api/pages') // highlight-line
.then((res) => res.json())
.then((data) => console.log(data))
```
For more information about the REST API, [click here](../rest-api/overview).
### GraphQL API
Payload automatically exposes GraphQL queries and mutations through a dedicated [GraphQL API](../graphql/overview). By default, the GraphQL route handler is mounted at the `/api/graphql` path of your app. You'll also find a full GraphQL Playground which can be accessible at the `/api/graphql-playground` path of your app.
You can use any GraphQL client with Payload's GraphQL endpoint. Here are a few packages:
- [`graphql-request`](https://www.npmjs.com/package/graphql-request) - a very lightweight GraphQL client
- [`@apollo/client`](https://www.apollographql.com/docs/react/api/core/ApolloClient/) - an industry-standard GraphQL client with lots of nice features
For more information about the GraphQL API, [click here](../graphql/overview).
## Package Structure
Payload is abstracted into a set of dedicated packages to keep the core `payload` package as lightweight as possible. This allows you to only install the parts of Payload based on your unique project requirements.
**Important:** Version numbers of all official Payload packages are always
published in sync. You should make sure that you always use matching versions
for all official Payload packages.
`payload`
The `payload` package is where core business logic for Payload lives. You can think of Payload as an ORM with superpowers—it contains the logic for all Payload "operations" like `find`, `create`, `update`, and `delete` and exposes a [Local API](../local-api/overview). It executes [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Validation](../fields/overview#validation), and more.
Payload itself is extremely compact, and can be used in any Node environment. As long as you have `payload` installed and you have access to your Payload Config, you can query and mutate your database directly without going through an unnecessary HTTP layer.
Payload also contains all TypeScript definitions, which can be imported from `payload` directly.
Here's how to import some common Payload types:
```ts
import { Config, CollectionConfig, GlobalConfig, Field } from 'payload'
```
`@payloadcms/next`
Whereas Payload itself is responsible for direct database access, and control over Payload business logic, the `@payloadcms/next` package is responsible for the Admin Panel and the entire HTTP layer that Payload exposes, including the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview).
`@payloadcms/graphql`
All of Payload's GraphQL functionality is abstracted into a separate package. Payload, its Admin UI, and REST API have absolutely no overlap with GraphQL, and you will incur no performance overhead from GraphQL if you are not using it. However, it's installed within the `@payloadcms/next` package so you don't have to install it manually. You do, however, need to have GraphQL installed separately in your `package.json` if you are using GraphQL.
`@payloadcms/ui`
This is the UI library that Payload's Admin Panel uses. All components are exported from this package and can be re-used as you build extensions to the Payload admin UI, or want to use Payload components in your own React apps. Some exports are server components and some are client components.
`@payloadcms/db-postgres`, `@payloadcms/db-vercel-postgres`, `@payloadcms/db-mongodb`, `@payloadcms/db-sqlite`
You can choose which Database Adapter you'd like to use for your project, and no matter which you choose, the entire data layer for Payload is contained within these packages. You can only use one at a time for any given project.
`@payloadcms/richtext-lexical`, `@payloadcms/richtext-slate`
Payload's Rich Text functionality is abstracted into separate packages and if you want to enable Rich Text in your project, you'll need to install one of these packages. We recommend Lexical for all new projects, and this is where Payload will focus its efforts on from this point, but Slate is still supported if you have already built with it.
**Note:** Rich Text is entirely optional and you may not need it for your
project.
# Installation
Source: https://payloadcms.com/docs/getting-started/installation
## Software Requirements
Payload requires the following software:
- Any JavaScript package manager (pnpm, npm, or yarn - pnpm is preferred)
- Node.js version 20.9.0+
- Any [compatible database](../database/overview) (MongoDB, Postgres or SQLite)
**Important:** Before proceeding any further, please ensure that you have the
above requirements met.
## 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
```
Then just follow the prompts! You'll get set up with a new folder and a functioning Payload app inside. You can then start [configuring your application](../configuration/overview).
## Adding to an existing app
Adding Payload to an existing Next.js app is super straightforward. You can either run the `npx create-payload-app` command inside your Next.js project's folder, or manually install Payload by following the steps below.
If you don't have a Next.js app already, but you still want to start a project from a blank Next.js app, you can create a new Next.js app using `npx create-next-app` - and then just follow the steps below to install Payload.
**Note:** Next.js version 15 or higher is required for Payload.
#### 1. Install the relevant packages
First, you'll want to add the required Payload packages to your project and can do so by running the command below:
```bash
pnpm i payload @payloadcms/next @payloadcms/richtext-lexical sharp graphql
```
**Note:** Swap out `pnpm` for your package manager. If you are using npm, you
might need to install using legacy peer deps: `npm i --legacy-peer-deps`.
Next, install a [Database Adapter](../database/overview). Payload requires a Database Adapter to establish a database connection. Payload works with all types of databases, but the most common are MongoDB and Postgres.
To install a Database Adapter, you can run **one** of the following commands:
- To install the [MongoDB Adapter](../database/mongodb), run:
```bash
pnpm i @payloadcms/db-mongodb
```
- To install the [Postgres Adapter](../database/postgres), run:
```bash
pnpm i @payloadcms/db-postgres
```
- To install the [SQLite Adapter](../database/sqlite), run:
```bash
pnpm i @payloadcms/db-sqlite
```
**Note:** New [Database Adapters](../database/overview) are becoming
available every day. Check the docs for the most up-to-date list of what's
available.
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/%28payload%29) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/
├─ (payload)/
├── // Payload files
├─ (my-app)/
├── // Your app files
```
_For an exact reference of the `(payload)` directory, see [Project Structure](../admin/overview#project-structure)._
You may need to copy all of your existing frontend files, including your
existing root layout, into its own newly created [Route
Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups),
i.e. `(my-app)`.
The files that Payload needs to have in your `/app` folder do not regenerate, and will never change. Once you slot them in, you never have to revisit them. They are not meant to be edited and simply import Payload dependencies from `@payloadcms/next` for the REST / GraphQL API and Admin Panel.
You can name the `(my-app)` folder anything you want. The name does not matter and will just be used to clarify your directory structure for yourself. Common names might be `(frontend)`, `(app)`, or similar. [More details](../admin/overview).
#### 3. Add the Payload Plugin to your Next.js config
Payload has a Next.js plugin that it uses to ensure compatibility with some of the packages Payload relies on, like `mongodb` or `drizzle-kit`.
To add the Payload Plugin, use `withPayload` in your `next.config.js`:
```js
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Your Next.js config here
experimental: {
reactCompiler: false,
},
}
// Make sure you wrap your `nextConfig`
// with the `withPayload` plugin
export default withPayload(nextConfig) // highlight-line
```
**Important:** Payload is a fully ESM project, and that means the
`withPayload` function is an ECMAScript module.
To import the Payload Plugin, you need to make sure your `next.config` file is set up to use ESM.
You can do this in one of two ways:
1. Set your own project to use ESM, by adding `"type": "module"` to your `package.json` file
2. Give your Next.js config the `.mjs` file extension
In either case, all `require`s and `export`s in your `next.config` file will need to be converted to `import` / `export` if they are not set up that way already.
#### 4. Create a Payload Config and add it to your TypeScript config
Finally, you need to create a [Payload Config](../configuration/overview). Generally the Payload Config is located at the root of your repository, or next to your `/app` folder, and is named `payload.config.ts`.
Here's what Payload needs at a bare minimum:
```ts
import sharp from 'sharp'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { buildConfig } from 'payload'
export default buildConfig({
// If you'd like to use Rich Text, pass your editor here
editor: lexicalEditor(),
// Define and configure your collections in this array
collections: [],
// Your Payload secret - should be a complex and secure string, unguessable
secret: process.env.PAYLOAD_SECRET || '',
// Whichever Database Adapter you're using should go here
// Mongoose is shown as an example, but you can also use Postgres
db: mongooseAdapter({
url: process.env.DATABASE_URI || '',
}),
// If you want to resize images, crop, set focal point, etc.
// make sure to install it and pass it to the config.
// This is optional - if you don't need to do these things,
// you don't need it!
sharp,
})
```
Although this is just the bare minimum config, there are _many_ more options that you can control here. To reference the full config and all of its options, [click here](../configuration/overview).
Once you have a Payload Config, update your `tsconfig` to include a `path` that points to it:
```json
{
"compilerOptions": {
"paths": {
"@payload-config": ["./payload.config.ts"]
}
}
}
```
#### 5. Fire it up!
After you've reached this point, it's time to boot up Payload. Start your project in your application's folder to get going. By default, the Next.js dev script is `pnpm dev` (or `npm run dev` if using npm).
After it starts, you can go to `http://localhost:3000/admin` to create your first Payload user!
# The Payload Config
Source: https://payloadcms.com/docs/configuration/overview
Payload is a _config-based_, code-first CMS and application framework. The Payload Config is central to everything that Payload does, allowing for deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon.
Everything from your [Database](../database/overview) choice to the appearance of the [Admin Panel](../admin/overview) is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
The Payload Config is a `payload.config.ts` file typically located in the root of your project:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// Your config goes here
})
```
The Payload Config is strongly typed and ties directly into Payload's TypeScript codebase. This means your IDE (such as VSCode) will provide helpful information like type-ahead suggestions while you write your config.
**Tip:** The location of your Payload Config can be customized. [More
details](#customizing-the-config-location).
## Config Options
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data through [Fields](../fields/overview).
Here is one of the simplest possible Payload configs:
```ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
export default buildConfig({
secret: process.env.PAYLOAD_SECRET,
db: mongooseAdapter({
url: process.env.DATABASE_URI,
}),
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
},
],
},
],
})
```
**Note:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
The following options are available:
| Option | Description |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../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 to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
| **`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 details](../upload/overview#payload-wide-upload-options). |
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
| **`onInit`** | A function that is called immediately following startup that receives the Payload instance as its only argument. |
| **`debug`** | Enable to expose more detailed error information. |
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
| **`secret`** \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
_\* An asterisk denotes that a property is required._
**Note:** Some properties are removed from the client-side bundle. [More
details](../custom-components/overview#accessing-the-payload-config).
### TypeScript Config
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](./collections) and [Globals](./globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
To customize the TypeScript settings, use the `typescript` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
typescript: {
// ...
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
## Config Location
For Payload command-line scripts, we need to be able to locate your Payload Config. We'll check a variety of locations for the presence of `payload.config.ts` by default, including:
1. The root current working directory
1. The `compilerOptions` in your `tsconfig`\*
1. The `dist` directory\*
_\* Config location detection is different between development and production environments. See below for more details._
**Important:** Ensure your `tsconfig.json` is properly configured for Payload
to auto-detect your config location. If it does not exist, or does not specify
the proper `compilerOptions`, Payload will default to the current working
directory.
**Development Mode**
In development mode, if the configuration file is not found at the root, Payload will attempt to read your `tsconfig.json`, and attempt to find the config file specified in the `rootDir`:
```json
{
// ...
// highlight-start
"compilerOptions": {
"rootDir": "src"
}
// highlight-end
}
```
**Production Mode**
In production mode, Payload will first attempt to find the config file in the `outDir` of your `tsconfig.json`, and if not found, will fallback to the `rootDir` directory:
```json
{
// ...
// highlight-start
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
}
// highlight-end
}
```
If none was in either location, Payload will finally check the `dist` directory.
### Customizing the Config Location
In addition to the above automated detection, you can specify your own location for the Payload Config. This can be useful in situations where your config is not in a standard location, or you wish to switch between multiple configurations. To do this, Payload exposes an [Environment Variable](../configuration/environment-vars) to bypass all automatic config detection.
To use a custom config location, set the `PAYLOAD_CONFIG_PATH` environment variable:
```json
{
"scripts": {
"payload": "PAYLOAD_CONFIG_PATH=/path/to/custom-config.ts payload"
}
}
```
**Tip:** `PAYLOAD_CONFIG_PATH` can be either an absolute path, or path
relative to your current working directory.
## 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).
## Cross-origin resource sharing (CORS)#cors
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or an object with the following properties:
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **`origins`** | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
| **`headers`** | A list of allowed headers that will be appended in `Access-Control-Allow-Headers`. |
Here's an example showing how to allow incoming requests from any domain:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
cors: '*',
// highlight-end
})
```
Here's an example showing how to append a new header (`x-custom-header`) in `Access-Control-Allow-Headers`:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
cors: {
origins: ['http://localhost:3000'],
headers: ['x-custom-header'],
},
// highlight-end
})
```
## TypeScript
You can import types from Payload to help make writing your config easier and type-safe. There are two main types that represent the Payload Config, `Config` and `SanitizedConfig`.
The `Config` type represents a raw Payload Config in its full form. Only the bare minimum properties are marked as required. The `SanitizedConfig` type represents a Payload Config after it has been fully sanitized. Generally, this is only used internally by Payload.
```ts
import type { Config, SanitizedConfig } from 'payload'
```
## Server vs. Client
The Payload Config only lives on the server and is not allowed to contain any client-side code. That way, you can load up the Payload Config in any server environment or standalone script, without having to use Bundlers or Node.js loaders to handle importing client-only modules (e.g. scss files or React Components) without any errors.
Behind the curtains, the Next.js-based Admin Panel generates a ClientConfig, which strips away any server-only code and enriches the config with React Components.
## Compatibility flags
The Payload Config can accept compatibility flags for running the newest versions but with older databases. You should only use these flags if you need to, and should confirm that you need to prior to enabling these flags.
`allowLocalizedWithinLocalized`
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
## Custom bin scripts
Using the `bin` configuration property, you can inject your own scripts to `npx payload`.
Example for `pnpm payload seed`:
Step 1: create `seed.ts` file in the same folder with `payload.config.ts` with:
```ts
import type { SanitizedConfig } from 'payload'
import payload from 'payload'
// Script must define a "script" function export that accepts the sanitized config
export const script = async (config: SanitizedConfig) => {
await payload.init({ config })
await payload.create({
collection: 'pages',
data: { title: 'my title' },
})
payload.logger.info('Successfully seeded!')
process.exit(0)
}
```
Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
bin: [
{
scriptPath: path.resolve(dirname, 'seed.ts'),
key: 'seed',
},
],
})
```
Now you can run the command using:
```sh
pnpm payload seed
```
## Running bin scripts on a schedule
Every bin script supports being run on a schedule using cron syntax. Simply pass the `--cron` flag followed by the cron expression when running the script. Example:
```sh
pnpm payload run ./myScript.ts --cron "0 * * * *"
```
This will use the `run` bin script to execute the specified script on the defined schedule.
# Collection Configs
Source: https://payloadcms.com/docs/configuration/collections
A Collection is a group of records, called Documents, that all share a common schema. You can define as many Collections as your application needs. Each Document in a Collection is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates a [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) used to manage your Documents.
Collections are also used to achieve [Authentication](../authentication/overview) in Payload. By defining a Collection with `auth` options, that Collection receives additional operations to support user authentication.
Collections are the primary way to structure recurring data in your application, such as users, products, pages, posts, and other types of content that you might want to manage. Each Collection can have its own unique [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Admin Options](#admin-options), and more.
To define a Collection Config, use the `collection` property in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
collections: [
// highlight-line
// Your Collections go here
],
})
```
**Tip:** If your Collection is only ever meant to contain a single Document,
consider using a [Global](./globals) instead.
## Config Options
It's often best practice to write your Collections in separate files and then import them into the main [Payload Config](./overview).
Here is what a simple Collection Config might look like:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
},
],
}
```
**Reminder:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
The following options are available:
| Option | Description |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `access` | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `disableDuplicate` | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| `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. Multiple fields can be specified by using a string array. |
| `dbName` | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| `endpoints` | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `enableQueryPresets` | Enable query presets for this Collection. [More details](../query-presets/overview). |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `trash` | A boolean to enable soft deletes for this collection. Defaults to `false`. [More details](../trash/overview). |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks. [More details](../queries/select). |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |
_\* An asterisk denotes that a property is required._
### Fields
Fields define the schema of the Documents within a Collection. To learn more, go to the [Fields](../fields/overview) documentation.
### Access Control
[Collection Access Control](../access-control/overview) determines what a user can and cannot do with any given Document within a Collection. To learn more, go to the [Access Control](../access-control/overview) documentation.
### Hooks
[Collection Hooks](../hooks/collections) allow you to tie into the lifecycle of your Documents so you can execute your own logic during specific events. To learn more, go to the [Hooks](../hooks/overview) documentation.
## Admin Options
The behavior of Collections within the [Admin Panel](../admin/overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](../custom-components/overview), selecting which fields to display in the List View, and more.
To configure Admin Options for Collections, use the `admin` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The following options are available:
| Option | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `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 details](../hooks/collections). |
| `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 to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `groupBy` | Beta. Enable grouping by a field in the list view. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../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](../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. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `formatDocURL` | Function to customize document links in the List View. Return `null` to disable linking, or a string for custom URLs. [More details](#format-document-urls). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `enableListViewSelectAPI` | Performance opt-in. When `true`, uses the Select API in the List View to query only the active columns as opposed to entire documents. [More details](#enable-list-view-select-api). |
| `pagination` | Set pagination-specific options for this Collection in the List View. [More details](#pagination). |
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
only the ID of the related document(s) as the title. To display a specific
field (i.e. title) from the related document instead, create a virtual field
that extracts the desired data, and set `useAsTitle` to that virtual field.
### Custom Components
Collections can set their own [Custom Components](../custom-components/overview) which only apply to Collection-specific UI within the [Admin Panel](../admin/overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Collection Components, use the `admin.components` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-line
// ...
},
},
}
```
The following options are available:
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `afterList` | An array of components to inject _after_ the built-in List View. [More details](../custom-components/list-view#afterlist). |
| `afterListTable` | An array of components to inject _after_ the built-in List View's table. [More details](../custom-components/list-view#afterlisttable). |
| `beforeList` | An array of components to inject _before_ the built-in List View. [More details](../custom-components/list-view#beforelist). |
| `beforeListTable` | An array of components to inject _before_ the built-in List View's table. [More details](../custom-components/list-view#beforelisttable). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. [More details](../custom-components/list-view#description). |
| `edit` | Override specific components within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
#### Edit View Options
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-line
// ...
},
},
},
}
```
The following options are available:
| Option | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
### Pagination
All Collections receive their own List View which displays a paginated list of documents that can be sorted and filtered. The pagination behavior of the List View can be customized on a per-Collection basis, and uses the same [Pagination](../queries/pagination) API that Payload provides.
To configure pagination options, use the `admin.pagination` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
pagination: {
defaultLimit: 10,
limits: [10, 20, 50],
},
// highlight-end
},
}
```
The following options are available:
| 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. |
### List Searchable Fields
In the List View, there is a "search" box that allows you to quickly find a document through a simple text search. By default, it searches on the ID field. If defined, the `admin.useAsTitle` field is used. Or, you can explicitly define which fields to search based on the needs of your application.
To define which fields should be searched, use the `admin.listSearchableFields` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
listSearchableFields: ['title', 'slug'],
// highlight-end
},
}
```
**Tip:** If you are adding `listSearchableFields`, make sure you index each of
these fields so your admin queries can remain performant.
## Enable List View Select API
When `true`, the List View will use the [Select API](../queries/select) to query only the _active_ columns as opposed to entire documents. This can greatly improve performance, especially for collections with large documents or many fields.
To enable this, set `enableListViewSelectAPI: true` in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// ...
// highlight-start
enableListViewSelectAPI: true,
// highlight-end
},
}
```
**Note:** The `enableListViewSelectAPI` property is labeled as experimental,
as it will likely become the default behavior in v4 and be deprecated.
Enabling this feature may cause unexpected behavior in some cases, however, such as when using hooks that rely on the full document data.
For example, if your component relies on a "title" field, this field will no longer be populated if the column is inactive:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
hooks: {
afterRead: [
({ doc }) => doc.title, // The `title` field will no longer be populated by default, unless the column is active
],
},
},
],
}
```
To ensure title is always present, you will need to add that field to the [`forceSelect`](../queries/select) property in your Collection Config:
```ts
export const Posts: CollectionConfig = {
// ...
forceSelect: {
title: true,
},
}
```
### Format Document URLs
The `formatDocURL` function allows you to customize how document links are generated in the List View. This is useful for disabling links for certain documents, redirecting to custom destinations, or modifying URLs based on user context or document state.
To define a custom document URL formatter, use the `admin.formatDocURL` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
formatDocURL: ({ doc, defaultURL, req, collectionSlug, viewType }) => {
// Disable linking for documents with specific status
if (doc.status === 'private') {
return null
}
// Custom destination for featured posts
if (doc.featured) {
return '/admin/featured-posts'
}
// Add query parameters based on user role
if (req.user?.role === 'admin') {
return defaultURL + '?admin=true'
}
// Use default URL for all other cases
return defaultURL
},
},
}
```
The `formatDocURL` function receives the following arguments:
| Argument | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `doc` | The document data for the current row |
| `defaultURL` | The default URL that Payload would normally generate for this document. You can return this as-is, modify it, or replace it entirely. |
| `req` | The full [PayloadRequest](../types/payload-request) object, providing access to user context, payload instance, and other request data |
| `collectionSlug` | The slug of the current collection |
| `viewType` | The current view context (`'list'`, `'trash'`, etc.) where the link is being generated |
The function should return:
- `null` to disable the link entirely (no link will be rendered)
- A `string` containing the custom URL to use for the link
- The `defaultURL` parameter to use Payload's default linking behavior
**Tip:** The `defaultURL` parameter saves you from having to reconstruct URLs
manually. You can modify it by appending query parameters or use it as a
fallback for your custom logic.
#### Examples
**Disable linking for certain users:**
```ts
formatDocURL: ({ defaultURL, req }) => {
if (req.user?.role === 'editor') {
return null // No link rendered
}
return defaultURL
}
```
## GraphQL
You can completely disable GraphQL for this collection by passing `graphQL: false` to your collection config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.
You can also pass an object to the collection's `graphQL` property, which allows you to define the following properties:
| Option | Description |
| ------------------ | ----------------------------------------------------------------------------------- |
| `singularName` | Override the "singular" name that will be used in GraphQL schema generation. |
| `pluralName` | Override the "plural" name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this collection by passing `true`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this collection by passing `true`. |
## TypeScript
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizedCollectionConfig`.
The `CollectionConfig` type represents a raw Collection Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedCollectionConfig` type represents a Collection Config after it has been fully sanitized. Generally, this is only used internally by Payload.
```ts
import type { CollectionConfig, SanitizedCollectionConfig } from 'payload'
```
# Global Configs
Source: https://payloadcms.com/docs/configuration/globals
Globals are in many ways similar to [Collections](./collections), except that they correspond to only a single Document. You can define as many Globals as your application needs. Each Global Document is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates a [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) used to manage your Documents.
Globals are the primary way to structure singletons in Payload, such as a header navigation, site-wide banner alerts, or app-wide localized strings. Each Global can have its own unique [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Admin Options](#admin-options), and more.
To define a Global Config, use the `globals` property in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
globals: [
// highlight-line
// Your Globals go here
],
})
```
**Tip:** If you have more than one Global that share the same structure,
consider using a [Collection](./collections) instead.
## Config Options
It's often best practice to write your Globals in separate files and then import them into the main [Payload Config](./overview).
Here is what a simple Global Config might look like:
```ts
import { GlobalConfig } from 'payload'
export 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,
},
],
},
],
}
```
**Reminder:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
The following options are available:
| Option | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access` | Provide Access Control functions to define exactly who should be able to do what with this Global. [More details](../access-control/globals). |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `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. |
| `description` | Text or React component to display below the Global header to give editors more information. |
| `endpoints` | Add custom routes to the REST API. [More details](../rest-api/overview#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties related to this global. [More details](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#global-hooks). |
| `label` | Text for the name in the Admin Panel or an object with keys for each language. Auto-generated from slug if not defined. |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Global. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#global-config). |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks. [More details](../queries/select). |
_\* An asterisk denotes that a property is required._
### Fields
Fields define the schema of the Global. To learn more, go to the [Fields](../fields/overview) documentation.
### Access Control
[Global Access Control](../access-control/globals) determines what a user can and cannot do with any given Global Document. To learn more, go to the [Access Control](../access-control/overview) documentation.
### Hooks
[Global Hooks](../hooks/globals) allow you to tie into the lifecycle of your Documents so you can execute your own logic during specific events. To learn more, go to the [Hooks](../hooks/overview) documentation.
## Admin Options
The behavior of Globals within the [Admin Panel](../admin/overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](../custom-components/overview), setting page metadata, and more.
To configure Admin Options for Globals, use the `admin` property in your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The following options are available:
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `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 details](#custom-components). |
| `preview` | Function to generate a preview URL within the Admin Panel for this Global that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
| `meta` | Page metadata overrides to apply to this Global within the Admin Panel. [More details](../admin/metadata). |
### Custom Components
Globals can set their own [Custom Components](../custom-components/overview) which only apply to Global-specific UI within the [Admin Panel](../admin/overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Global Components, use the `admin.components` property in your Global Config:
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: {
// highlight-line
// ...
},
},
}
```
The following options are available:
#### General
| Option | Description |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| `elements` | Override or create new elements within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
#### Edit View Options
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: {
elements: {
// highlight-line
// ...
},
},
},
}
```
The following options are available:
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
## GraphQL
You can completely disable GraphQL for this global by passing `graphQL: false` to your global config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.
You can also pass an object to the global's `graphQL` property, which allows you to define the following properties:
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------- |
| `name` | Override the name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this global by passing `true`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this global by passing `true`. |
## TypeScript
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizedGlobalConfig`.
The `GlobalConfig` type represents a raw Global Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedGlobalConfig` type represents a Global Config after it has been fully sanitized. Generally, this is only used internally by Payload.
```ts
import type { GlobalConfig, SanitizedGlobalConfig } from 'payload'
```
# I18n
Source: https://payloadcms.com/docs/configuration/i18n
The [Admin Panel](../admin/overview) is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/main/packages/translations). With I18n, editors can navigate the interface and read API error messages in their preferred language. This is similar to [Localization](./localization), but instead of managing translations for the data itself, you are managing translations for your application's interface.
By default, Payload comes preinstalled with English, but you can easily load other languages into your own application. Languages are automatically detected based on the request. If no language is detected, or if the user's language is not yet supported by your application, English will be chosen.
To add I18n to your project, you first need to install the `@payloadcms/translations` package:
```bash
pnpm install @payloadcms/translations
```
Once installed, it can be configured using the `i18n` key in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
i18n: {
// highlight-line
// ...
},
})
```
**Note:** If there is a language that Payload does not yet support, we accept
[code
contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
## Config Options
You can easily customize and override any of the i18n settings that Payload provides by default. Payload will use your custom options and merge them in with its own.
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
i18n: {
fallbackLanguage: 'en', // default
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `fallbackLanguage` | The language to fall back to if the user's preferred language is not supported. Default is `'en'`. |
| `translations` | An object containing the translations. The keys are the language codes and the values are the translations. |
| `supportedLanguages` | An object containing the supported languages. The keys are the language codes and the values are the translations. |
## Adding Languages
You can easily add new languages to your Payload app by providing the translations for the new language. Payload maintains a number of built-in translations that can be imported from `@payloadcms/translations`, but you can also provide your own [Custom Translations](#custom-translations) to support any language.
To add a new language, use the `i18n.supportedLanguages` key in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
import { en } from '@payloadcms/translations/languages/en'
import { de } from '@payloadcms/translations/languages/de'
export default buildConfig({
// ...
// highlight-start
i18n: {
supportedLanguages: { en, de },
},
// highlight-end
})
```
**Tip:** It's best to only support the languages that you need so that the
bundled JavaScript is kept to a minimum for your project.
### Custom Translations
You can customize Payload's built-in translations either by extending existing languages or by adding new languages entirely. This can be done by injecting new translation strings into existing languages, or by providing an entirely new language keys altogether.
To add Custom Translations, use the `i18n.translations` key in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
//...
i18n: {
// highlight-start
translations: {
en: {
custom: {
// namespace can be anything you want
key1: 'Translation with {{variable}}', // translation
},
// override existing translation keys
general: {
dashboard: 'Home',
},
},
},
// highlight-end
},
//...
})
```
### Project Translations
While Payload's built-in features come fully translated, you may also want to translate parts of your own project. This is possible in places like [Collections](./collections) and [Globals](./globals), such as on their labels and groups, field labels, descriptions or input placeholder text.
To do this, provide the translations wherever applicable, keyed to the language code:
```ts
import type { CollectionConfig } from 'payload'
export const Articles: CollectionConfig = {
slug: 'articles',
labels: {
singular: {
// highlight-start
en: 'Article',
es: 'Artículo',
// highlight-end
},
plural: {
// highlight-start
en: 'Articles',
es: 'Artículos',
// highlight-end
},
},
admin: {
group: {
// highlight-start
en: 'Content',
es: 'Contenido',
// highlight-end
},
},
fields: [
{
name: 'title',
type: 'text',
label: {
// highlight-start
en: 'Title',
es: 'Título',
// highlight-end
},
admin: {
placeholder: {
// highlight-start
en: 'Enter title',
es: 'Introduce el título',
// highlight-end
},
},
},
],
}
```
## Changing Languages
Users can change their preferred language in their account settings or by otherwise manipulating their [User Preferences](../admin/preferences).
## Node.js#node
Payload's backend sets 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 Payload's extensive internationalization features assigned to `req.i18n`. To access text translations you can use `req.t('namespace:key')`.
## TypeScript
In order to use [Custom Translations](#custom-translations) in your project, you need to provide the types for the translations.
Here we create a shareable translations object. We will import this in both our custom components and in our Payload config.
In this example we show how to extend English, but you can do the same for any language you want.
```ts
// /custom-translations.ts
import { enTranslations } from '@payloadcms/translations/languages/en'
import type { NestedKeysStripped } from '@payloadcms/translations'
export const customTranslations = {
en: {
general: {
myCustomKey: 'My custom english translation',
},
fields: {
addLabel: 'Add!',
},
},
}
export type CustomTranslationsObject = typeof customTranslations.en &
typeof enTranslations
export type CustomTranslationsKeys =
NestedKeysStripped
```
Import the shared translations object into our Payload config so they are available for use:
```ts
// /payload.config.ts
import { buildConfig } from 'payload'
import { customTranslations } from './custom-translations'
export default buildConfig({
//...
i18n: {
translations: customTranslations,
},
//...
})
```
Import the shared translation types to use in your [Custom Component](../custom-components/overview):
```ts
// /components/MyComponent.tsx
'use client'
import type React from 'react'
import { useTranslation } from '@payloadcms/ui'
import type {
CustomTranslationsObject,
CustomTranslationsKeys,
} from '../custom-translations'
export const MyComponent: React.FC = () => {
const { i18n, t } = useTranslation<
CustomTranslationsObject,
CustomTranslationsKeys
>() // These generics merge your custom translations with the default client translations
return t('general:myCustomKey')
}
```
Additionally, Payload exposes the `t` function in various places, for example in labels. Here is how you would type those:
```ts
// /fields/myField.ts
import type {
DefaultTranslationKeys,
TFunction,
} from '@payloadcms/translations'
import type { Field } from 'payload'
import { CustomTranslationsKeys } from '../custom-translations'
const field: Field = {
name: 'myField',
type: 'text',
label: ({ t: defaultT }) => {
const t = defaultT as TFunction
return t('fields:addLabel')
},
}
```
# Localization
Source: https://payloadcms.com/docs/configuration/localization
Localization is one of the most important features of a modern CMS. It allows you to manage content in multiple languages, then serve it to your users based on their requested language. This is similar to [I18n](./i18n), but instead of managing translations for your application's interface, you are managing translations for the data itself.
With Localization, you can begin to serve personalized content to your users based on their specific language preferences, such as a multilingual website or multi-site application. There are no limits to the number of locales you can add to your Payload project.
To configure Localization, use the `localization` key in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
localization: {
// highlight-line
// ...
},
})
```
## Config Options
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.
To configure locales, use the `localization.locales` property in your [Payload Config](./overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
localization: {
locales: ['en', 'es', 'de'], // required
defaultLocale: 'en', // required
},
})
```
You can also define locales using [full configuration objects](#locale-object):
```ts
import { buildConfig } from 'payload'
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, // defaults to true
},
})
```
**Tip:** Localization works very well alongside
[I18n](../configuration/i18n).
The following options are available:
| Option | Description |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`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 unless a fallback is explicitly provided in the request. True by default. |
| **`filterAvailableLocales`** | A function that is called with the array of `locales` and the `req`, it should return locales to show in admin UI selector. [See more](#filter-available-options). |
### Locales
The locales array is a list of all the languages that you would like to support. This can be strings for each language code, or [full configuration objects](#locale-object) for more advanced options.
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 3166‑1) such as `en-US`, `en-UK`, `es-MX`, etc.
#### Locale Object
| 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. This can be a single locale or array of locales. |
_\* An asterisk denotes that a property is required._
#### Filter Available Options
In some projects you may want to filter the available locales shown in the admin UI selector. You can do this by providing a `filterAvailableLocales` function in your Payload Config. This is called on the server side and is passed the array of locales. This means that you can determine what locales are visible in the localizer selection menu at the top of the admin panel. You could do this per user, or implement a function that scopes these to tenants and more. Here is an example using request headers in a multi-tenant application:
```ts
// ... rest of Payload config
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
filterAvailableLocales: async ({ req, locales }) => {
if (getTenantFromCookie(req.headers, 'text')) {
const fullTenant = await req.payload.findByID({
id: getTenantFromCookie(req.headers, 'text') as string,
collection: 'tenants',
req,
})
if (fullTenant && fullTenant.supportedLocales?.length) {
return locales.filter((locale) => {
return fullTenant.supportedLocales?.includes(locale.code as 'en' | 'es')
})
}
}
return locales
},
}
```
Since the filtering happens at the root level of the application and its result is not calculated every time you navigate to a new page, you may want to call `router.refresh` in a custom component that watches when values that affect the result change. In the example above, you would want to do this when `supportedLocales` changes on the tenant document.
## 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.
**Note:** 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.
**Important:** 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.
## 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](../graphql/overview#graphql-playground).
The `fallbackLocale` arg will accept valid locales, an array of locales, as well as `none` to disable falling back.
**Example:**
```graphql
query {
Posts(locale: de, fallbackLocale: none) {
docs {
title
}
}
}
```
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.
#### 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, array of locales, as well as `'null'`, `'false'`, `false`, and `'none'`.
**Example:**
```js
const posts = await payload.find({
collection: 'posts',
locale: 'es',
fallbackLocale: false,
})
```
**Tip:** The REST and Local APIs can return all Localization data in one
request by passing 'all' or '*' as the **locale** 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.
# Environment Variables
Source: https://payloadcms.com/docs/configuration/environment-vars
Environment Variables are a way to store sensitive information that your application needs to function. This could be anything from API keys to [Database](../database/overview) credentials. Payload allows you to easily use Environment Variables within your config and throughout your application.
## Next.js Applications
If you are using Next.js, no additional setup is required other than creating your `.env` file.
To use Environment Variables, add a `.env` file to the root of your project:
```plaintext
project-name/
├─ .env
├─ package.json
├─ payload.config.ts
```
Here is an example of what an `.env` file might look like:
```plaintext
SERVER_URL=localhost:3000
DATABASE_URI=mongodb://localhost:27017/my-database
```
To use Environment Variables in your Payload Config, you can access them directly from `process.env`:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
serverURL: process.env.SERVER_URL, // highlight-line
// ...
})
```
## Client-side Environments
For security and safety reasons, the [Admin Panel](../admin/overview) does **not** include Environment Variables in its _client-side_ bundle by default. But, Next.js provides a mechanism to expose Environment Variables to the client-side bundle when needed.
If you are building a [Custom Component](../custom-components/overview) and need to access Environment Variables from the client-side, you can do so by prefixing them with `NEXT_PUBLIC_`.
**Important:** 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 sensitive information. Only ever include keys that are safe for the
public to read in plain text.
For example, if you've got the following Environment Variable:
```bash
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_XXXXXXXXXXXXXXXXXX
```
This key will automatically be made available to the client-side Payload bundle and can be referenced in your Custom Component as follows:
```tsx
'use client'
import React from 'react'
const stripeKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY // highlight-line
const MyClientComponent = () => {
// do something with the key
return
My Client Component
}
```
For more information, check out the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).
## Outside of Next.js
If you are using Payload outside of Next.js, we suggest using the [`dotenv`](https://www.npmjs.com/package/dotenv) package to handle Environment Variables from `.env` files. This will automatically load your Environment Variables into `process.env`.
To do this, import the package as high up in your application as possible:
```ts
import dotenv from 'dotenv'
dotenv.config() // highlight-line
import { buildConfig } from 'payload'
export default buildConfig({
serverURL: process.env.SERVER_URL,
// ...
})
```
**Tip:** Be sure that `dotenv` can find your `.env` file. By default, it will
look for a file named `.env` in the root of your project. If you need to
specify a different file, pass the path into the config options.
# Database
Source: https://payloadcms.com/docs/database/overview
Payload is database agnostic, meaning you can use any type of database behind Payload's familiar APIs. Payload is designed to interact with your database through a Database Adapter, which is a thin layer that translates Payload's internal data structures into your database's native data structures.
Currently, Payload officially supports the following Database Adapters:
- [MongoDB](../database/mongodb) with [Mongoose](https://mongoosejs.com/)
- [Postgres](../database/postgres) with [Drizzle](https://drizzle.team/)
- [SQLite](../database/sqlite) with [Drizzle](https://drizzle.team/)
To configure a Database Adapter, use the `db` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
export default buildConfig({
// ...
// highlight-start
db: mongooseAdapter({
url: process.env.DATABASE_URI,
}),
// highlight-end
})
```
**Reminder:** The Database Adapter is an external dependency and must be
installed in your project separately from Payload. You can find the
installation instructions for each Database Adapter in their respective
documentation.
## 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.
There are two main categories of databases to choose from:
- [Non-Relational Databases](#non-relational-databases)
- [Relational Databases](#relational-databases)
### Non-Relational Databases
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](../configuration/localization)
- You leverage a lot of [Arrays](../fields/array), [Blocks](../fields/blocks), or `hasMany` [Select](../fields/select) fields
### Relational Databases
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 or SQLite if:
- You are comfortable with [Migrations](./migrations)
- You require enforced data consistency at the database level
- You have a lot of relationships between collections and require relationships to be enforced
## Payload Differences
It's important to note that nearly every Payload feature is available in all of our officially supported Database Adapters, including [Localization](../configuration/localization), [Arrays](../fields/array), [Blocks](../fields/blocks), etc. The only thing that is not supported in SQLite yet is the [Point Field](../fields/point), but that should be added soon.
It's up to you to choose which database you would like to use based on the requirements of your project. Payload has no opinion on which database you should ultimately choose.
# Migrations
Source: https://payloadcms.com/docs/database/migrations
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"
}
}
```
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.
## 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.
Here is an example migration file:
```ts
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
export async function up({ payload, req }: MigrateUpArgs): Promise {
// 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 {
// Do whatever you need to revert changes if the `up` function fails
}
```
## Using Transactions
When migrations are run, each migration is performed in a new [transaction](../database/transactions) for you. All
you need to do is pass the `req` object to any [Local API](../local-api/overview) or direct database calls, such as
`payload.db.updateMany()`, to make database changes inside the transaction. Assuming no errors were thrown, the transaction is committed
after your `up` or `down` function runs. If the migration errors at any point or fails to commit, it is caught and the
transaction gets aborted. This way no change is made to the database if the migration fails.
### Using database directly with the transaction
Additionally, you can bypass Payload's layer entirely and perform operations directly on your underlying database within the active transaction:
### MongoDB:
```ts
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
export async function up({
session,
payload,
req,
}: MigrateUpArgs): Promise {
const posts = await payload.db.collections.posts.collection
.find({ session })
.toArray()
}
```
### Postgres:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise {
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
```
### SQLite:
In SQLite, transactions are disabled by default. [More](./transactions).
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
export async function up({ db, payload, req }: MigrateUpArgs): Promise {
const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
```
## 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
```
Flags:
- `--skip-empty`: with Postgres, it skips the "no schema changes detected. Would you like to create a blank migration file?" prompt which can be useful for generating migration in CI.
- `--force-accept-warning`: accepts any command prompts, creates a blank migration even if there weren't any changes to the schema.
### 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
```
## When to run migrations
Depending on which Database Adapter you use, your migration workflow might differ subtly.
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
#### MongoDB#mongodb-migrations
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
#### Postgres#postgres-migrations
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).
That means that Postgres users of Payload should become familiar with the entire migration workflow from top to bottom.
Here is an overview of a common workflow for working locally against a development database, creating migrations, and then running migrations against your production database before deploying.
**1 - work locally using push mode**
Payload uses Drizzle ORM's powerful `push` mode to automatically sync data changes to your database for you while in development mode. By default, this is enabled and is the suggested workflow to using Postgres and Payload while doing local development.
You can disable this setting and solely use migrations to manage your local development database (pass `push: false` to your Postgres adapter), but if you do disable it, you may see frequent errors while running development mode. This is because Payload will have updated to your new data shape, but your local database will not have updated.
For this reason, we suggest that you leave `push` as its default setting and treat your local dev database as a sandbox.
For more information about push mode and prototyping in development, [click here](./postgres#prototyping-in-development-mode).
The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.
But importantly, you do not need to run migrations against your development database, because Drizzle will have already pushed your changes to your database for you.
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.
**2 - create a migration**
Once you're done with working in your Payload Config, you can create a migration. It's best practice to try and complete a specific task or fully build out a feature before you create a migration.
But once you're ready, you can run `pnpm payload migrate:create`, which will perform the following steps for you:
- We will look for any existing migrations, and automatically generate SQL changes necessary to convert your schema from its prior state to the new state of your Payload Config
- We will then create a new migration file in your `/migrations` folder that contains all the SQL necessary to be run
We won't immediately run this migration for you, however.
Tip: migrations created by Payload are relatively programmatic in nature, so
there should not be any surprises, but before you check in the created
migration it's a good idea to always double-check the contents of the
migration files.
**3 - set up your build process to run migrations**
Generally, you want to run migrations before you build Payload for production. This typically happens in your CI pipeline and can usually be configured on platforms like Payload Cloud, Vercel, or Netlify by specifying your build script.
A common set of scripts in a `package.json`, set up to run migrations in CI, might look like this:
```js
"scripts": {
// For running in dev mode
"dev": "next dev --turbo",
// To build your Next + Payload app for production
"build": "next build",
// A "tie-in" to Payload's CLI for convenience
// this helps you run `pnpm payload migrate:create` and similar
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
// This command is what you'd set your `build script` to.
// Notice how it runs `payload migrate` and then `pnpm build`?
// This will run all migrations for you before building, in your CI,
// against your production database
"ci": "payload migrate && pnpm build",
},
```
In the example above, we've specified a `ci` script which we can use as our "build script" in the platform that we are deploying to production with.
This will require that your build pipeline can connect to your database, and it will simply run the `payload migrate` command prior to starting the build process. By calling `payload migrate`, Payload will automatically execute any migrations in your `/migrations` folder that have not yet been executed against your production database, in the order that they were created.
If it fails, the deployment will be rejected. But now, with your build script set up to run your migrations, you will be all set! Next time you deploy, your CI will execute the required migrations for you, and your database will be caught up with the shape that your Payload Config requires.
## Running migrations in production
In certain cases, you might want to run migrations at runtime when the server starts. Running them during build time may be impossible due to not having access to your database connection while building or similar reasoning.
If you're using a long-running server or container where your Node server starts up one time and then stays initialized, you might prefer to run migrations on server startup instead of within your CI.
In order to run migrations at runtime, on initialization, you can pass your migrations to your database adapter under the `prodMigrations` key as follows:
```ts
// Import your migrations from the `index.ts` file
// that Payload generates for you
import { migrations } from './migrations'
import { buildConfig } from 'payload'
export default buildConfig({
// your config here
db: postgresAdapter({
// your adapter config here
prodMigrations: migrations,
}),
})
```
Passing your migrations as shown above will tell Payload, in production only, to execute any migrations that need to be run prior to completing the initialization of Payload. This is ideal for long-running services where Payload will only be initialized at startup.
**Warning:** if Payload is instructed to run migrations in production, this
may slow down serverless cold starts on platforms such as Vercel. Generally,
this option should only be used for long-running servers / containers.
## Environment-Specific Configurations and Migrations
Your configuration may include environment-specific settings (e.g., enabling a plugin only in production). If you generate migrations without considering the environment, it can lead to discrepancies and issues. When running migrations locally, Payload uses the development environment, which might miss production-specific configurations. Similarly, running migrations in production could miss development-specific entities.
This is an easy oversight, so be mindful of any environment-specific logic in your config when handling migrations.
**Ways to address this:**
- Manually update your migration file after it is generated to include any environment-specific configurations.
- Temporarily enable any required production environment variables in your local setup when generating the migration to capture the necessary updates.
- Use separate migration files for each environment to ensure the correct migration is executed in the corresponding environment.
# Transactions
Source: https://payloadcms.com/docs/database/transactions
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 data changing 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.
**Note:**
MongoDB requires a connection to a replicaset in order to make use of transactions.
**Note:**
Transactions in SQLite are disabled by default. You need to pass `transactionOptions: {}` to enable them.
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 outside of Payload's Local API.
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.
- `payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
- `payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
Payload uses the `req` object to pass the transaction ID through to the database adapter. If you are not using the `req` object, you can make a new object to pass the transaction ID directly to database adapter methods and Local API calls.
Example:
```ts
import payload from 'payload'
import config from './payload.config'
const standalonePayloadScript = async () => {
// initialize Payload
await payload.init({ config })
const transactionID = await payload.db.beginTransaction()
try {
// Make an update using the Local API
await payload.update({
collection: 'posts',
data: {
some: 'data',
},
where: {
slug: { equals: 'my-slug' },
},
req: { transactionID },
})
/*
You can make additional db changes or run other functions
that need to be committed on an all or nothing basis
*/
// Commit the transaction
await payload.db.commitTransaction(transactionID)
} catch (error) {
// Rollback the transaction
await payload.db.rollbackTransaction(transactionID)
}
}
standalonePayloadScript()
```
## Disabling Transactions
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the Local API by adding `disableTransaction: true` to the args. For example:
```ts
await payload.update({
collection: 'posts',
data: {
some: 'data',
},
where: {
slug: { equals: 'my-slug' },
},
disableTransaction: true,
})
```
# Indexes
Source: https://payloadcms.com/docs/database/indexes
Database indexes are a way to optimize the performance of your database by allowing it to quickly locate and retrieve data. If you have a field that you frequently query or sort by, adding an index to that field can significantly improve the speed of those operations.
When your query runs, the database will not scan the entire document to find that one field, but will instead use the index to quickly locate the data.
To index a field, set the `index` option to `true` in your field's config:
```ts
import type { CollectionConfig } from 'payload'
export MyCollection: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'title',
type: 'text',
// highlight-start
index: true,
// highlight-end
},
]
}
```
**Note:** The `id`, `createdAt`, and `updatedAt` fields are indexed by
default.
**Tip:** If you're using MongoDB, you can use [MongoDB
Compass](https://www.mongodb.com/products/compass) to visualize and manage
your indexes.
## Compound Indexes
In addition to indexing single fields, you can also create compound indexes that index multiple fields together. This can be useful for optimizing queries that filter or sort by multiple fields.
To create a compound index, use the `indexes` option in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
fields: [
// ...
],
indexes: [
{
fields: ['title', 'createdAt'],
unique: true, // Optional, if you want the combination of fields to be unique
},
],
}
```
## Localized fields and MongoDB indexes
When you set `index: true` or `unique: true` on a localized field, MongoDB creates one index **per locale path** (e.g., `slug.en`, `slug.da-dk`, etc.). With many locales and indexed fields, this can quickly approach MongoDB's per-collection index limit.
If you know you'll query specifically by a locale, you can insert a custom MongoDB index for the locale path manually or with a migration script.
# MongoDB
Source: https://payloadcms.com/docs/database/mongodb
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. |
| `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. |
| `collectionsSchemaOptions` | Customize Mongoose schema options for collections. |
| `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/). |
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `disableFallbackSort` | Set to `true` to disable the adapter adding a fallback sort when sorting by non-unique fields, this can affect performance in some cases but it ensures a consistent order of results. |
| `useAlternativeDropDatabase` | Set to `true` to use an alternative `dropDatabase` implementation that calls `collection.deleteMany({})` on every collection instead of sending a raw `dropDatabase` command. Payload only uses `dropDatabase` for testing purposes. Defaults to `false`. |
| `useBigIntForNumberIDs` | Set to `true` to use `BigInt` for custom ID fields of type `'number'`. Useful for databases that don't support `double` or `int32` IDs. Defaults to `false`. |
| `useJoinAggregations` | Set to `false` to disable join aggregations (which use correlated subqueries) and instead populate join fields via multiple `find` queries. Defaults to `true`. |
| `usePipelineInSortLookup` | Set to `false` to disable the use of `pipeline` in the `$lookup` aggregation in sorting. Defaults to `true`. |
## 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]`
## Using other MongoDB implementations
You can import the `compatibilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
```ts
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
export default buildConfig({
db: mongooseAdapter({
url: process.env.DATABASE_URI,
// For example, if you're using firestore:
...compatibilityOptions.firestore,
}),
})
```
We export compatibility options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
- Azure Cosmos DB does not support transactions that update two or more documents in different collections, which is a common case when using Payload (via hooks).
- Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.
# Postgres
Source: https://payloadcms.com/docs/database/postgres
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.
Alternatively, the `@payloadcms/db-vercel-postgres` package is also available and is optimized for use with Vercel.
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:
### Usage
`@payloadcms/db-postgres`:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
export default buildConfig({
// Configure the Postgres adapter here
db: postgresAdapter({
// Postgres-specific arguments go here.
// `pool` is required.
pool: {
connectionString: process.env.DATABASE_URI,
},
}),
})
```
`@payloadcms/db-vercel-postgres`:
```ts
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'
export default buildConfig({
// Automatically uses process.env.POSTGRES_URL if no options are provided.
db: vercelPostgresAdapter(),
// Optionally, can accept the same options as the @vercel/postgres package.
db: vercelPostgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL,
},
}),
})
```
**Note:** If you're using `vercelPostgresAdapter` your
`process.env.POSTGRES_URL` or `pool.connectionString` points to a local
database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module
for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres`
doesn't work with local databases, if you want to disable that behavior, you
can pass `forceUseVercelPostgres: true` to the adapter's args and follow
[Vercel
guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker)
for a Docker Neon DB setup.
## 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` or to `@vercel/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. |
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `disableCreateDatabase` | Pass `true` to disable auto database creation if it doesn't exist. Defaults to `false`. |
| `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'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `readReplicas` | An array of DB read replicas connection strings, can be used to offload read-heavy traffic. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
import { posts } from './payload-generated-schema'
// To avoid installing Drizzle, you can import everything that drizzle has from our re-export path.
import { eq, sql, and } from '@payloadcms/db-postgres/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## 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`](../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
In Postgres, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
For more information about migrations, [click here](./migrations#when-to-run-migrations).
## Drizzle schema hooks
### beforeSchemaInit
Runs before the schema is built. You can use this hook to extend your database structure with tables that won't be managed by Payload.
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import {
integer,
pgTable,
serial,
} from '@payloadcms/db-postgres/drizzle/pg-core'
postgresAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
addedTable: pgTable('added_table', {
id: serial('id').notNull(),
}),
},
}
},
],
})
```
One use case is preserving your existing database structure when migrating to Payload. By default, Payload drops the current database schema, which may not be desirable in this scenario.
To quickly generate the Drizzle schema from your database you can use [Drizzle Introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull)
You should get the `schema.ts` file which may look like this:
```ts
import {
pgTable,
uniqueIndex,
serial,
varchar,
text,
} from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
fullName: text('full_name'),
phone: varchar('phone', { length: 256 }),
})
export const countries = pgTable(
'countries',
{
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
},
(countries) => {
return {
nameIndex: uniqueIndex('name_idx').on(countries.name),
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { users, countries } from '../drizzle/schema'
postgresAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
users,
countries,
},
}
},
],
})
```
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { index, integer } from '@payloadcms/db-postgres/drizzle/pg-core'
import { buildConfig } from 'payload'
export default buildConfig({
collections: [
{
slug: 'places',
fields: [
{
name: 'country',
type: 'text',
},
{
name: 'city',
type: 'text',
},
],
},
],
db: postgresAdapter({
afterSchemaInit: [
({ schema, extendTable, adapter }) => {
extendTable({
table: schema.tables.places,
columns: {
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
return schema
},
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
postgresAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
// Add a new table
adapter.rawTables.myTable = {
name: 'my_table',
columns: {
my_id: {
name: 'my_id',
type: 'serial',
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
adapter.rawTables.posts.columns.customColumn = {
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column'],
}
return schema
},
],
})
```
# SQLite
Source: https://payloadcms.com/docs/database/sqlite
To use Payload with SQLite, install the package `@payloadcms/db-sqlite`. It leverages Drizzle ORM and `libSQL` to interact with a SQLite database that you provide.
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 SQLite, pass the `sqliteAdapter` to your Payload Config as follows:
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Configure the SQLite adapter here
db: sqliteAdapter({
// SQLite-specific arguments go here.
// `client.url` is required.
client: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_AUTH_TOKEN,
},
}),
})
```
## Options
| Option | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
| `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. |
| `idType` | A string of 'number', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `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'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `autoIncrement` | Pass `true` to enable SQLite [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for primary keys to ensure the same ID cannot be reused from deleted rows |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
// Import table from the generated file
import { posts } from './payload-generated-schema'
// To avoid installing Drizzle, you can import everything that drizzle has from our re-export path.
import { eq, sql, and } from '@payloadcms/db-sqlite/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## Tables and relations
In addition to exposing Drizzle directly, all of the tables and Drizzle relations are exposed for you via the `payload.db` property as well.
- Tables - `payload.db.tables`
- 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`](../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
In SQLite, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
For more information about migrations, [click here](./migrations#when-to-run-migrations).
## Drizzle schema hooks
### beforeSchemaInit
Runs before the schema is built. You can use this hook to extend your database structure with tables that won't be managed by Payload.
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
import { integer, sqliteTable } from '@payloadcms/db-sqlite/drizzle/sqlite-core'
sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
addedTable: sqliteTable('added_table', {
id: integer('id').primaryKey({ autoIncrement: true }),
}),
},
}
},
],
})
```
One use case is preserving your existing database structure when migrating to Payload. By default, Payload drops the current database schema, which may not be desirable in this scenario.
To quickly generate the Drizzle schema from your database you can use [Drizzle Introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull)
You should get the `schema.ts` file which may look like this:
```ts
import {
sqliteTable,
text,
uniqueIndex,
integer,
} from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
fullName: text('full_name'),
phone: text('phone', { length: 256 }),
})
export const countries = sqliteTable(
'countries',
{
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name', { length: 256 }),
},
(countries) => {
return {
nameIndex: uniqueIndex('name_idx').on(countries.name),
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
import { users, countries } from '../drizzle/schema'
sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
users,
countries,
},
}
},
],
})
```
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
import { index, integer } from '@payloadcms/db-sqlite/drizzle/sqlite-core'
import { buildConfig } from 'payload'
export default buildConfig({
collections: [
{
slug: 'places',
fields: [
{
name: 'country',
type: 'text',
},
{
name: 'city',
type: 'text',
},
],
},
],
db: sqliteAdapter({
afterSchemaInit: [
({ schema, extendTable, adapter }) => {
extendTable({
table: schema.tables.places,
columns: {
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
return schema
},
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
// Add a new table
adapter.rawTables.myTable = {
name: 'my_table',
columns: {
my_id: {
name: 'my_id',
type: 'integer',
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
adapter.rawTables.posts.columns.customColumn = {
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column'],
}
return schema
},
],
})
```
## D1 Database
This adapter is currently in beta as it is new and could be subject to changes
which may be considered breaking
We also provide a separate adapter to connect to [Cloudflare D1](https://developers.cloudflare.com/d1/), which is a serverless SQLite database.
To use it, install the package `@payloadcms/db-d1-sqlite` and configure it as follows:
```ts
import { sqliteD1Adapter } from '@payloadcms/db-d1-sqlite'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Configure the D1 adapter here
db: sqliteD1Adapter({
// D1-specific arguments go here.
// `binding` is required and should match the D1 database binding name in your Cloudflare Worker environment.
binding: cloudflare.env.D1,
}),
})
```
It inherits the options from the SQLite adapter above with the exception of the connection options in favour of the `binding`.
You can see our [Cloudflare D1 template](https://github.com/payloadcms/payload/tree/main/templates/with-cloudflare-d1) for a full example of how to set this up.
### D1 Read Replicas
You can enable read replicas support with the `first-primary` strategy. This is experimental.
You must also enable it on your D1 database in the Cloudflare dashboard. Read more about it in the [Cloudflare documentation](https://developers.cloudflare.com/d1/best-practices/read-replication/).
All write queries are still forwarded to the primary database instance. Read
replication only improves the response time for read query requests.
```ts
import { sqliteD1Adapter } from '@payloadcms/db-d1-sqlite'
export default buildConfig({
collections: [],
db: sqliteD1Adapter({
binding: cloudflare.env.D1,
// You can also enable read replicas support with the `first-primary` strategy.
readReplicas: 'first-primary',
}),
})
```
You can then verify that they're being used by checking the logs in your Cloudflare dashboard. You should see logs indicating whether a read or write operation was performed, and on which database instance.
# Fields Overview
Source: https://payloadcms.com/docs/fields/overview
Fields are the building blocks of Payload. They define the schema of the Documents that will be stored in the [Database](../database/overview), as well as automatically generate the corresponding UI within the [Admin Panel](../admin/overview).
There are many [Field Types](#field-types) to choose from, ranging anywhere from simple text strings to nested arrays and blocks. Most fields save data to the database, while others are strictly presentational. Fields can have [Custom Validations](#validation), [Conditional Logic](./overview#conditional-logic), [Access Control](#field-level-access-control), [Hooks](#field-level-hooks), and so much more.
Fields can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#custom-components).
To configure fields, use the `fields` property in your [Collection](../configuration/collections) or [Global](../configuration/globals) config:
```ts
import type { CollectionConfig } from 'payload'
export const Page: CollectionConfig = {
// ...
fields: [
// highlight-line
// ...
],
}
```
## Field Types
Payload provides a wide variety of built-in Field Types, each with its own unique properties and behaviors that determine which values it can accept, how it is presented in the API, and how it will be rendered in the [Admin Panel](../admin/overview).
To configure fields, use the `fields` property in your [Collection](../configuration/collections) or [Global](../configuration/globals) config:
```ts
import type { CollectionConfig } from 'payload'
export const Page: CollectionConfig = {
slug: 'pages',
// highlight-start
fields: [
{
name: 'field',
type: 'text',
},
],
// highlight-end
}
```
**Reminder:** Each field is an object with at least the `type` property. This
matches the field to its corresponding Field Type. [More
details](#field-options).
There are three main categories of fields in Payload:
- [Data Fields](#data-fields)
- [Presentational Fields](#presentational-fields)
- [Virtual Fields](#virtual-fields)
To begin writing fields, first determine which [Field Type](#field-types) best supports your application. Then author your field accordingly using the [Field Options](#field-options) for your chosen field type.
### Data Fields
Data Fields are used to store data in the [Database](../database/overview). All Data Fields have a `name` property. This is the key that will be used to store the field's value.
Here are the available Data Fields:
- [Array](./array) - for repeating content, supports nested fields
- [Blocks](./blocks) - for block-based content, supports nested fields
- [Checkbox](./checkbox) - saves boolean true / false values
- [Code](./code) - renders a code editor interface that saves a string
- [Date](./date) - renders a date picker and saves a timestamp
- [Email](./email) - ensures the value is a properly formatted email address
- [Group](./group) - nests fields within a keyed object
- [JSON](./json) - renders a JSON editor interface that saves a JSON object
- [Number](./number) - saves numeric values
- [Point](./point) - for location data, saves geometric coordinates
- [Radio](./radio) - renders a radio button group that allows only one value to be selected
- [Relationship](./relationship) - assign relationships to other collections
- [Rich Text](./rich-text) - renders a fully extensible rich text editor
- [Select](./select) - renders a dropdown / picklist style value selector
- [Tabs (Named)](./tabs) - similar to group, but renders nested fields within a tabbed layout
- [Text](./text) - simple text input that saves a string
- [Textarea](./textarea) - similar to text, but allows for multi-line input
- [Upload](./upload) - allows local file and image upload
### Presentational Fields
Presentational Fields do not store data in the database. Instead, they are used to organize and present other fields in the [Admin Panel](../admin/overview), or to add custom UI components.
Here are the available Presentational Fields:
- [Collapsible](../fields/collapsible) - nests fields within a collapsible component
- [Row](../fields/row) - aligns fields horizontally
- [Tabs (Unnamed)](../fields/tabs) - nests fields within a tabbed layout. It is not presentational if the tab has a name.
- [Group (Unnamed)](../fields/group) - nests fields within a keyed object It is not presentational if the group has a name.
- [UI](../fields/ui) - blank field for custom UI components
### Virtual Fields
Virtual fields display data that is not stored in the database, but is computed or derived from other fields.
Here are the available Virtual Fields:
- [Join](../fields/join) - achieves two-way data binding between fields
**Tip:** Don't see a built-in field type that you need? Build it! Using a
combination of [Field Validations](#validation) and [Custom
Components](../custom-components/overview), you can override the entirety of
how a component functions within the [Admin Panel](../admin/overview) to
effectively create your own field type.
## Virtual Field Configuration
While [Join fields](#virtual-fields) are purpose-built virtual field types, **any field type can be made virtual** by adding the `virtual` property to its configuration. This allows you to create computed or relationship-derived fields that appear in API responses without being stored in the database.
Virtual fields are populated during API responses and can be used in the Admin Panel, but their values are not persisted to the database. This makes them ideal for displaying read-only computed data, relationship summaries, or formatted versions of existing field data.
### Configuring Virtual Fields
Any field type can be made virtual by adding the `virtual` property to the field configuration. The `virtual` property can be configured in two ways:
#### Boolean Virtual Fields
When `virtual` is set to `true`, the field becomes virtual but doesn't automatically populate any data. You'll typically use [Field-level Hooks](#field-level-hooks) to compute and populate the field's value:
```ts
{
name: 'fullName',
type: 'text',
virtual: true,
hooks: {
afterRead: [
({ siblingData }) => {
return `${siblingData.firstName} ${siblingData.lastName}`
}
]
}
}
```
#### String Path Virtual Fields
When `virtual` is set to a string path, it creates a "virtual relationship field" that automatically resolves to data from another field in the document. This is particularly useful for displaying relationship data:
```ts
{
name: 'authorName',
type: 'text',
virtual: 'author.name' // Resolves to the 'name' field of the 'author' relationship
}
```
### Virtual Path Syntax
Virtual paths use dot notation to traverse relationships and nested data:
- `author.name` - Gets the `name` field from the `author` relationship
- `author.profile.bio` - Gets the `bio` field from a nested `profile` object within the `author` relationship
- `categories.title` - For hasMany relationships, returns an array of `title` values
- `request.additionalStakeholders.email` - Traverses multiple relationship levels
**Important Requirements for Virtual Path Fields:**
1. **Source Relationship Required**: The document must have a relationship field that corresponds to the first part of the virtual path. For example, if using `virtual: 'author.name'`, there must be an `author` relationship field defined in the same collection.
2. **Path Resolution**: Virtual paths resolve at query time by following the relationships and extracting the specified field values.
3. **Array Handling**: When the virtual path traverses a `hasMany` relationship, the result will be an array of values.
### Common Use Cases
#### Displaying Relationship Names
Instead of just showing relationship IDs, display the actual names or titles:
```ts
// Original relationship field
{
name: 'author',
type: 'relationship',
relationTo: 'users'
},
// Virtual field to display author's name
{
name: 'authorName',
type: 'text',
virtual: 'author.name'
}
```
#### Multiple Relationship Values
For `hasMany` relationships, virtual fields return arrays:
```ts
// Original relationship field
{
name: 'categories',
type: 'relationship',
relationTo: 'categories',
hasMany: true
},
// Virtual field to display category titles
{
name: 'categoryTitles',
type: 'text',
virtual: 'categories.title' // Returns ['Tech', 'News', 'Updates']
}
```
#### Computed Values
Use hooks to create computed virtual fields:
```ts
{
name: 'wordCount',
type: 'number',
virtual: true,
hooks: {
afterRead: [
({ siblingData }) => {
const content = siblingData.content || ''
return content.split(/\s+/).length
}
]
}
}
```
### Virtual Fields in API Responses
Virtual fields appear in API responses alongside regular fields:
```json
{
"id": "123",
"title": "My Post",
"author": "64f1234567890abcdef12345",
"authorName": "John Doe", // Virtual field
"categories": ["64f9876543210fedcba67890", "64f5432109876543210abcdef"],
"categoryTitles": ["Tech", "News"], // Virtual field
"wordCount": 450 // Virtual field
}
```
**Important:** When using virtual path fields, ensure that the referenced
relationship field exists in your schema. Virtual paths like `author.name`
require an `author` relationship field to be defined, otherwise the virtual
field will not resolve properly.
## Field Options
All fields require at least the `type` property. This matches the field to its corresponding [Field Type](#field-types) to determine its appearance and behavior within the [Admin Panel](../admin/overview). Each Field Type has its own unique set of options based on its own type.
To set a field's type, use the `type` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text', // highlight-line
name: 'myField',
}
```
For a full list of configuration options, see the documentation for each
[Field Type](#field-types).
### Field Names
All [Data Fields](#data-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 amongst this field's siblings.
To set a field's name, use the `name` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField', // highlight-line
}
```
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`
- `status` - with Postgres Adapter and when drafts are enabled
### Field-level Hooks
In addition to being able to define [Hooks](../hooks/overview) on a document-level, you can define extremely granular logic field-by-field.
To define Field-level Hooks, use the `hooks` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
// highlight-start
hooks: {
// ...
},
// highlight-end
}
```
For full details on Field-level Hooks, see the [Field Hooks](../hooks/fields) documentation.
### Field-level Access Control
In addition to being able to define [Access Control](../access-control/overview) on a document-level, you can define extremely granular permissions field-by-field.
To define Field-level Access Control, use the `access` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
// highlight-start
access: {
// ...
},
// highlight-end
}
```
For full details on Field-level Access Control, see the [Field Access Control](../access-control/fields) documentation.
### Default Values
Fields can be optionally prefilled with initial values. This is used in both the [Admin Panel](../admin/overview) as well as API requests to populate missing or undefined field values during the `create` or `update` operations.
To set a field's default value, use the `defaultValue` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
defaultValue: 'Hello, World!', // highlight-line
}
```
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's [Database Adapters](../database/overview) will apply it to the database schema or models.
Functions can be written to make use of the following argument properties:
- `user` - the authenticated user object
- `locale` - the currently selected locale string
- `req` - the `PayloadRequest` object
Here is an example of a `defaultValue` function:
```ts
import type { Field } from 'payload'
const translation: {
en: 'Written by'
es: 'Escrito por'
}
export const myField: Field = {
name: 'attribution',
type: 'text',
// highlight-start
defaultValue: ({ user, locale, req }) =>
`${translation[locale]} ${user.name}`,
// highlight-end
}
```
**Tip:** You can use async `defaultValue` functions to fill fields with data
from API requests or Local API using `req.payload`.
### Validation
Fields are automatically validated based on their [Field Type](#field-types) and other [Field Options](#field-options) such as `required` or `min` and `max` value constraints. If needed, however, field validations can be customized or entirely replaced by providing your own custom validation functions.
To set a custom field validation function, use the `validate` property in your Field Config:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
validate: (value) => Boolean(value) || 'This field is required', // highlight-line
}
```
Custom validation functions should return either `true` or a `string` representing the error message to display in API responses.
The following arguments are provided to the `validate` function:
| Argument | Description |
| -------- | ------------------------------------------------------------------------------- |
| `value` | The value of the field being validated. |
| `ctx` | An object with additional data and context. [More details](#validation-context) |
#### Validation Context
The `ctx` argument contains full document data, sibling field data, the current operation, and other useful information such as currently authenticated user:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
// highlight-start
validate: (val, { user }) =>
Boolean(user) || 'You must be logged in to save this field',
// highlight-end
}
```
The following additional properties are provided in the `ctx` object:
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data` | An object containing the full collection or global document currently being edited. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. |
| `operation` | Will be `create` or `update` depending on the UI action or API call. |
| `path` | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#validation-performance). |
#### Localized and Built-in Error Messages
You can return localized error messages by utilizing the translation function provided in the `req` object:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
validate: (value, { req: { t } }) =>
Boolean(value) || t('validation:required'), // highlight-line
}
```
This way you can use [Custom Translations](../configuration/i18n#custom-translations) as well as Payload's built in error messages (like `validation:required` used in the example above). For a full list of available translation strings, see the [english translation file](https://github.com/payloadcms/payload/blob/main/packages/translations/src/languages/en.ts) of Payload.
#### Reusing Default Field Validations
When using custom validation functions, Payload will use yours in place of the default. However, you might want to simply augment the default validation with your own custom logic.
To reuse default field validations, call them from within your custom validation function:
```ts
import { text } from 'payload/shared'
const field: Field = {
name: 'notBad',
type: 'text',
validate: (val, args) => {
if (val === 'bad') return 'This cannot be "bad"'
return text(val, args) // highlight-line
},
}
```
Here is a list of all default field validation functions:
```ts
import {
array,
blocks,
checkbox,
code,
date,
email,
json,
number,
point,
radio,
relationship,
richText,
select,
tabs,
text,
textarea,
upload,
} from 'payload/shared'
```
#### Validation Performance
When writing async or computationally heavy validation functions, it is important to consider the performance implications. Within the Admin Panel, validations are executed on every change to the field, so they should be as lightweight as possible and only run when necessary.
If you need to perform expensive validations, such as querying the database, consider using the `event` property in the `ctx` object to only run that particular validation on form submission.
To write asynchronous validation functions, use the `async` keyword to define your function:
```ts
import type { CollectionConfig } from 'payload'
export const Orders: CollectionConfig = {
slug: 'orders',
fields: [
{
name: 'customerNumber',
type: 'text',
// highlight-start
validate: async (val, { event }) => {
if (event === 'onChange') {
return true
}
// only perform expensive validation when the form is submitted
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.'
},
// highlight-end
},
],
}
```
For more performance tips, see the [Performance
documentation](../performance/overview).
## Custom ID Fields
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically.
To define a custom ID field, add a top-level field with the `name` property set to `id`:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
fields: [
{
name: 'id', // highlight-line
required: true,
type: 'number',
},
],
}
```
**Reminder:** The Custom ID Fields can only be of type [`Number`](./number) or
[`Text`](./text). Custom ID fields with type `text` must not contain `/` or
`.` characters.
## Admin Options
You can customize the appearance and behavior of fields within the [Admin Panel](../admin/overview) through the `admin` property of any Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
// highlight-line
// ...
},
},
],
}
```
The following options are available:
| Option | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../custom-components/overview) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`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`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) 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](../admin/overview) entirely. |
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
| **`disableGroupBy`** | Set `disableGroupBy` to `true` to prevent fields from appearing in the list view groupBy options. Defaults to `false`. |
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. Defaults to `false`. |
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. Defaults to `false`. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
### Field Descriptions
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
A description can be configured in three ways:
- As a string.
- As a function which returns a string. [More details](#description-functions).
- As a React component. [More details](#description).
To add a Custom Description to a field, use the `admin.description` property in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
description: 'Hello, world!', // highlight-line
},
},
],
}
```
**Reminder:** To replace the Field Description with a [Custom
Component](../custom-components/overview), use the
`admin.components.Description` property. [More details](#description).
#### Description Functions
Custom Descriptions can also be defined as a function. Description Functions are executed on the server and can be used to format simple descriptions based on the user's current [Locale](../configuration/localization).
To add a Description Function to a field, set the `admin.description` property to a _function_ in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
description: ({ t }) => `${t('Hello, world!')}`, // highlight-line
},
},
],
}
```
All Description Functions receive the following arguments:
| Argument | Description |
| -------- | ------------------------------------------------------------------------------------------------ |
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
**Note:** If you need to subscribe to live updates within your form, use a
Description Component instead. [More details](#description).
### 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 the following arguments:
| Argument | Description |
| ----------------- | -------------------------------------------------------------------------------- |
| **`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. |
| **`ctx`** | An object containing additional information about the field’s location and user. |
The `ctx` object:
| Property | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. |
| **`operation`** | A string relating to which operation the field type is currently executing within. |
| **`path`** | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| **`user`** | The currently authenticated user object. |
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, { blockData, path, user }) => {
if (data.enableGreeting) {
return true
} else {
return false
}
},
// highlight-end
},
},
]
}
```
### Custom Components
Within the [Admin Panel](../admin/overview), fields are represented in three distinct places:
- [Field](#field) - The actual form field rendered in the Edit View.
- [Cell](#cell) - The table cell component rendered in the List View.
- [Filter](#filter) - The filter component rendered in the List View.
- [Diff](#diff) - The Diff component rendered in the Version Diff View
To swap in Field Components with your own, use the `admin.components` property in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
// highlight-line
// ...
},
},
},
],
}
```
The following options are available:
| Component | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
| **`Diff`** | Override the default Diff component rendered in the Version Diff View. [More details](#diff). |
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput). |
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
#### Field
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To swap in your own Field Component, use the `admin.components.Field` property in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
},
],
}
```
_For details on how to build Custom Components, see [Building Custom Components](../custom-components/overview#building-custom-components)._
Instead of replacing the entire Field Component, you can alternately replace
or slot-in only specific parts by using the [`Label`](#label),
[`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and
[`afterInput`](#afterinput-and-beforinput) properties.
##### Default Props
All Field Components receive the following props by default:
| Property | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](../admin/preferences) for the document. |
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
| **`schemaPath`** | A string representing the direct, static path to the Field Config, i.e. `posts.myGroup.myArray.myField`. |
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
In addition to the above props, all Server Components will also receive the following props:
| Property | Description |
| ----------------- | ----------------------------------------------------------------------------- |
| **`clientField`** | The serializable Client Field Config. |
| **`field`** | The Field Config. |
| **`data`** | The current document being edited. |
| **`i18n`** | The [i18n](../configuration/i18n) object. |
| **`payload`** | The [Payload](../local-api/overview) class. |
| **`permissions`** | The field permissions based on the currently authenticated user. |
| **`siblingData`** | The data of the field's siblings. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`value`** | The value of the field at render-time. |
##### Sending and receiving values from the form
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](../admin/react-hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return setValue(e.target.value)} value={value} />
}
```
For a complete list of all available React hooks, see the [Payload React
Hooks](../admin/react-hooks) documentation. For additional help, see [Building
Custom Components](../custom-components/overview#building-custom-components).
##### TypeScript#field-component-types
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every Field Type and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
See each individual Field Type for exact type imports.
#### Cell
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To swap in your own Cell Component, use the `admin.components.Cell` property in your Field Config:
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
All Cell Components receive the same [Default Field Component Props](#field), plus the following:
| Property | Description |
| ------------- | --------------------------------------------------------------------- |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
#### Filter
The Filter Component is the actual input element rendered within the "Filter By" dropdown of the List View used to represent this field when building filters.
To swap in your own Filter Component, use the `admin.components.Filter` property in your Field Config:
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Filter: '/path/to/MyCustomFilterComponent', // highlight-line
},
},
}
```
All Custom Filter Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
#### Label
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To swap in your own Label Component, use the `admin.components.Label` property in your Field Config:
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
All Custom Label Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
##### TypeScript#label-component-types
When building Custom Label Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Label Component, one for every Field Type and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
#### Description
Alternatively to the [Description Property](#field-descriptions), you can also use a [Custom Component](../custom-components/overview) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To add a Description Component to a field, use the `admin.components.Description` property in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
},
},
},
],
}
```
All Custom Description Components receive the same [Default Field Component Props](#field).
For details on how to build a Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
##### TypeScript#description-component-types
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every Field Type and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
#### Error
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To swap in your own Error Component, use the `admin.components.Error` property in your Field Config:
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
All Error Components receive the [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
##### TypeScript#error-component-types
When building Custom Error Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Error Component, one for every Field Type and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
#### Diff
The Diff Component is rendered in the Version Diff view. It will only be visible in entities with versioning enabled,
To swap in your own Diff Component, use the `admin.components.Diff` property in your Field Config:
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Diff: '/path/to/MyCustomDiffComponent', // highlight-line
},
},
}
```
All Error Components receive the [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
##### TypeScript#diff-component-types
When building Custom Diff Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Diff Component, one for every Field Type and server/client environment. The convention is to append `DiffServerComponent` or `DiffClientComponent` to the type of field, i.e. `TextFieldDiffClientComponent`.
```tsx
import type {
TextFieldDiffServerComponent,
TextFieldDiffClientComponent,
// And so on for each Field Type
} from 'payload'
```
#### afterInput and beforeInput
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
},
},
},
],
}
```
All `afterInput` and `beforeInput` Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components, see [Building Custom Components](../custom-components/overview#building-custom-components).
## TypeScript
You can import the Payload `Field` type as well as other common types from the `payload` package. [More details](../typescript/overview).
```ts
import type { Field } from 'payload'
```
# Array Field
Source: https://payloadcms.com/docs/fields/array
The Array Field is used when you need to have a set of "repeating" [Fields](./overview). It stores an array of objects containing fields that you define. These fields can be of any type, including other arrays, to achieve infinitely nested data structures.
Arrays are useful for many different types of content from simple to complex, such as:
- A "slider" with an image ([upload field](../fields/upload)) and a caption ([text field](../fields/text))
- Navigational structures where editors can specify nav items containing pages ([relationship field](../fields/relationship)), an "open in new tab" [checkbox field](../fields/checkbox)
- Event agenda "timeslots" where you need to specify start & end time ([date field](../fields/date)), label ([text field](../fields/text)), and Learn More page [relationship](../fields/relationship)
To create an Array Field, set the `type` to `array` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
// highlight-start
type: 'array',
fields: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../fields/overview#field-names). |
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) 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](../admin/overview) and the backend. [More details](../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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](../database/postgres)). Auto-generated from name if not defined. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Array Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Array Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`components.RowLabel`** | React component to be rendered as the label on the array row. [Example](#row-label) |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
## Example
In this example, we have an Array Field called `slider` that contains a set of fields for a simple image slider. Each row in the array has a `title`, `image`, and `caption`. We also customize the row label to display the title if it exists, or a default label if it doesn't.
```ts
import type { CollectionConfig } from 'payload'
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',
},
],
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldServerComponent } from 'payload'
export const CustomArrayFieldServer: ArrayFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldClientComponent } from 'payload'
export const CustomArrayFieldClient: ArrayFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { ArrayFieldLabelServerComponent } from 'payload'
export const CustomArrayFieldLabelServer: ArrayFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import type { ArrayFieldLabelClientComponent } from 'payload'
import { FieldLabel } from '@payloadcms/ui'
import React from 'react'
export const CustomArrayFieldLabelClient: ArrayFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
### Row Label
```tsx
'use client'
import { useRowLabel } from '@payloadcms/ui'
export const ArrayRowLabel = () => {
const { data, rowNumber } = useRowLabel<{ title?: string }>()
const customLabel = `${data.title || 'Slide'} ${String(rowNumber).padStart(2, '0')} `
return
Custom Label: {customLabel}
}
```
# Blocks Field
Source: https://payloadcms.com/docs/fields/blocks
The Blocks Field is one of the most flexible tools in Payload. It stores an array of objects, where each object is a “block” with its own schema. Unlike a simple array (where every item looks the same), blocks let you mix and match different content types in any order.
This makes Blocks perfect for building dynamic, editor-friendly experiences, such as:
- A page builder with blocks like `Quote`, `CallToAction`, `Slider`, or `Gallery`.
- A form builder with block types like `Text`, `Select`, or `Checkbox`.
- An event agenda where each timeslot could be a `Break`, `Presentation`, or `BreakoutSession`.
To add a Blocks Field, set the `type` to `blocks` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
// highlight-start
type: 'blocks',
blocks: [
// ...
],
// highlight-end
}
```
This page is divided into two parts: first, the settings of the Blocks Field, and then the settings of the blocks inside it.
## Block Field
### Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../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 details](../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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
### Admin Options
To customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Blocks Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------ |
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
#### Customizing the way your block is rendered in Lexical
If you're using this block within the [Lexical editor](../rich-text/overview), you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.
- `admin.components.Label` - pass a custom React component here to customize the way that the label is rendered for this block
- `admin.components.Block` - pass a component here to completely override the way the block is rendered in Lexical with your own component
This is super handy if you'd like to present your editors with a very deliberate and nicely designed block "preview" right in your rich text.
For example, if you have a `gallery` block, you might want to actually render the gallery of images directly in your Lexical block. With the `admin.components.Block` property, you can do exactly that!
**Tip:** If you customize the way your block is rendered in Lexical, you can
import utility components to easily edit / remove your block - so that you
don't have to build all of this yourself.
To import these utility components for one of your custom blocks, you can import the following:
```ts
import {
// Edit block buttons (choose the one that corresponds to your usage)
// When clicked, this will open a drawer with your block's fields
// so your editors can edit them
InlineBlockEditButton,
BlockEditButton,
// Buttons that will remove this block from Lexical
// (choose the one that corresponds to your usage)
InlineBlockRemoveButton,
BlockRemoveButton,
// The label that should be rendered for an inline block
InlineBlockLabel,
// The default "container" that is rendered for an inline block
// if you want to re-use it
InlineBlockContainer,
// The default "collapsible" UI that is rendered for a regular block
// if you want to re-use it
BlockCollapsible,
} from '@payloadcms/richtext-lexical/client'
```
## Blocks Items
### Config Options
Blocks are defined as separate configs of their own.
**Tip:** 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.
| 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. Alternatively you can use `admin.components.Label` for greater control. |
| **`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](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../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](../database/postgres)). Auto-generated from slug if not defined. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
### Admin Options
Blocks are not fields, so they don’t inherit the base properties shared by all fields (not to be confused with the Blocks Field, documented above, which does). Here are their available admin options:
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------- |
| **`components.Block`** | Custom component for replacing the Block, including the header. |
| **`components.Label`** | Custom component for replacing the Block Label. |
| **`disableBlockName`** | Hide the blockName field by setting this value to `true`. |
| **`group`** | Text or localization object used to group this Block in the Blocks Drawer. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
### blockType, blockName, and block.label
Each block stores two pieces of data alongside your fields. The `blockType` identifies which schema to use and it is exactly the block’s `slug`. The `blockName` is an optional label you can give to a block to make editing and scanning easier.
The **label** is shared by all blocks of the same type and is defined in the block config via `label` with a fallback to `slug`. On the other hand, the **blockName** is specific to each block individually. You can hide the editable name with `admin.disableBlockName`.
If you provide `admin.components.Label`, that component replaces both the name and the label in the Admin UI.
| Property | Scope | Source | Visible in UI | Notes |
| ------------- | ---------- | ------------------------------------------ | ------------- | -------------------------------------------------------------------------------------------------- |
| `blockType` | Each block | The block’s `slug` | Not a header | Used to resolve which block schema to render |
| `blockName` | Each block | Editor input in the Admin | Yes | Optional label; hide with `admin.disableBlockName` or replace with custom `admin.components.Label` |
| `block.label` | Block type | `label` in block config or `slug` fallback | Yes | Shared by all blocks of that type. Can be replaced with custom `admin.components.Label` |
### Custom Components
#### Field
##### Server Component
```tsx
import type React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldServerComponent } from 'payload'
export const CustomBlocksFieldServer: BlocksFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
##### Client Component
```tsx
'use client'
import React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldClientComponent } from 'payload'
export const CustomBlocksFieldClient: BlocksFieldClientComponent = (props) => {
return
}
```
#### Label
##### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelServerComponent } from 'payload'
export const CustomBlocksFieldLabelServer: BlocksFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
##### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelClientComponent } from 'payload'
export const CustomBlocksFieldLabelClient: BlocksFieldLabelClientComponent = ({
label,
path,
required,
}) => {
return (
)
}
```
## Example
`collections/ExampleCollection.js`
```ts
import { Block, CollectionConfig } from 'payload'
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,
],
},
],
}
```
## Block References
If you have multiple blocks used in multiple places, your Payload Config can grow in size, potentially sending more data to the client and requiring more processing on the server. However, you can optimize performance by defining each block **once** in your Payload Config and then referencing its slug wherever it's used instead of passing the entire block config.
To do this, define the block in the `blocks` array of the Payload Config. Then, in the Blocks Field, pass the block slug to the `blockReferences` array - leaving the `blocks` array empty for compatibility reasons.
```ts
import { buildConfig } from 'payload'
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'
// Payload Config
const config = buildConfig({
// Define the block once
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'collection1',
fields: [
{
name: 'content',
type: 'blocks',
// Reference the block by slug
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
},
],
},
{
slug: 'collection2',
fields: [
{
name: 'editor',
type: 'richText',
editor: lexicalEditor({
features: [
BlocksFeature({
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
blocks: ['TextBlock'],
}),
],
}),
},
],
},
],
})
```
**Reminder:**
Blocks referenced in the `blockReferences` array are treated as isolated from the collection / global config. This has the following implications:
1. The block config cannot be modified or extended in the collection config. It will be identical everywhere it's referenced.
2. Access control for blocks referenced in the `blockReferences` are run only once - data from the collection will not be available in the block's access control.
## 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'
```
## Conditional Blocks
Blocks can be conditionally enabled using the `filterOptions` property on the blocks field. It allows you to provide a function that returns which block slugs should be available based on the given context.
### Behavior
- `filterOptions` is re-evaluated as part of the form state request, whenever the document data changes.
- If a block is present in the field but no longer allowed by `filterOptions`, a validation error will occur when saving.
### Example
```ts
{
name: 'blocksWithDynamicFilterOptions',
type: 'blocks',
filterOptions: ({ siblingData }) => {
return siblingData?.enabledBlocks?.length
? [siblingData.enabledBlocks] // allow only the matching block
: true // allow all blocks if no value is set
},
blocks: [
{ slug: 'block1', fields: [{ type: 'text', name: 'block1Text' }] },
{ slug: 'block2', fields: [{ type: 'text', name: 'block2Text' }] },
{ slug: 'block3', fields: [{ type: 'text', name: 'block3Text' }] },
// ...
],
}
```
In this example, the list of available blocks is determined by the enabledBlocks sibling field. If no value is set, all blocks remain available.
# Checkbox Field
Source: https://payloadcms.com/docs/fields/checkbox
The Checkbox Field saves a boolean in the database.
To add a Checkbox Field, set the `type` to `checkbox` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyCheckboxField: Field = {
// ...
type: 'checkbox', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Example
Here is an example of a Checkbox Field in a Collection:
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'enableCoolStuff', // required
type: 'checkbox', // required
label: 'Click me to see fanciness',
defaultValue: false,
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { CheckboxField } from '@payloadcms/ui'
import type { CheckboxFieldServerComponent } from 'payload'
export const CustomCheckboxFieldServer: CheckboxFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { CheckboxField } from '@payloadcms/ui'
import type { CheckboxFieldClientComponent } from 'payload'
export const CustomCheckboxFieldClient: CheckboxFieldClientComponent = (
props,
) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CheckboxFieldLabelServerComponent } from 'payload'
export const CustomCheckboxFieldLabelServer: CheckboxFieldLabelServerComponent =
({ clientField, path }) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CheckboxFieldLabelClientComponent } from 'payload'
export const CustomCheckboxFieldLabelClient: CheckboxFieldLabelClientComponent =
({ label, path, required }) => {
return (
)
}
```
# Code Field
Source: https://payloadcms.com/docs/fields/code
The Code Field saves a string in the database, but provides the [Admin Panel](../admin/overview) with a code editor styled interface.
To add a Code Field, set the `type` to `code` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
type: 'code', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyCodeField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Code Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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 type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'trackingCode', // required
type: 'code', // required
required: true,
admin: {
language: 'javascript',
},
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { CodeField } from '@payloadcms/ui'
import type { CodeFieldServerComponent } from 'payload'
export const CustomCodeFieldServer: CodeFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { CodeField } from '@payloadcms/ui'
import type { CodeFieldClientComponent } from 'payload'
export const CustomCodeFieldClient: CodeFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CodeFieldLabelServerComponent } from 'payload'
export const CustomCodeFieldLabelServer: CodeFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CodeFieldLabelClientComponent } from 'payload'
export const CustomCodeFieldLabelClient: CodeFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# JSON Field
Source: https://payloadcms.com/docs/fields/json
The JSON Field saves raw JSON to the database and provides the [Admin Panel](../admin/overview) with a code editor styled interface. This is different from the [Code Field](./code) which saves the value as a string in the database.
To add a JSON Field, set the `type` to `json` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
type: 'json', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the JSON Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The JSON Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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
```ts
import type { CollectionConfig } from 'payload'
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 type { CollectionConfig } from 'payload'
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 type { CollectionConfig } from 'payload'
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
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { JSONField } from '@payloadcms/ui'
import type { JSONFieldServerComponent } from 'payload'
export const CustomJSONFieldServer: JSONFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { JSONField } from '@payloadcms/ui'
import type { JSONFieldClientComponent } from 'payload'
export const CustomJSONFieldClient: JSONFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { JSONFieldLabelServerComponent } from 'payload'
export const CustomJSONFieldLabelServer: JSONFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { JSONFieldLabelClientComponent } from 'payload'
export const CustomJSONFieldLabelClient: JSONFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Collapsible Field
Source: https://payloadcms.com/docs/fields/collapsible
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.
To add a Collapsible Field, set the `type` to `collapsible` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
// highlight-start
type: 'collapsible',
fields: [
// ...
],
// highlight-end
}
```
## Config Options
| 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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Collapsible Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
## Example
```ts
import type { CollectionConfig } from 'payload'
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,
},
],
},
],
}
```
# Date Field
Source: https://payloadcms.com/docs/fields/date
The Date Field saves a Date in the database and provides the [Admin Panel](../admin/overview) with a customizable time picker interface.
To add a Date Field, set the `type` to `date` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
type: 'date', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Date Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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/v4.1.0/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
```ts
import type { CollectionConfig } from 'payload'
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',
},
},
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { DateTimeField } from '@payloadcms/ui'
import type { DateFieldServerComponent } from 'payload'
export const CustomDateFieldServer: DateFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { DateTimeField } from '@payloadcms/ui'
import type { DateFieldClientComponent } from 'payload'
export const CustomDateFieldClient: DateFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { DateFieldLabelServerComponent } from 'payload'
export const CustomDateFieldLabelServer: DateFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { DateFieldLabelClientComponent } from 'payload'
export const CustomDateFieldLabelClient: DateFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
## Timezones
To enable timezone selection on a Date field, set the `timezone` property to `true`:
```ts
{
name: 'date',
type: 'date',
timezone: true,
}
```
This will add a dropdown to the date picker that allows users to select a timezone. The selected timezone will be saved in the database along with the date in a new column named `date_tz`.
You can customise the available list of timezones in the [global admin config](../admin/overview#timezones).
**Good to know:**
The date itself will be stored in UTC so it's up to you to handle the conversion to the user's timezone when displaying the date in your frontend.
Dates without a specific time are normalised to 12:00 in the selected timezone.
# Email Field
Source: https://payloadcms.com/docs/fields/email
The Email Field enforces that the value provided is a valid email address.
To create an Email Field, set the `type` to `email` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
type: 'email', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Email Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------ | ------------------------------------------------------------------------- |
| **`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
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'contact', // required
type: 'email', // required
label: 'Contact Email Address',
required: true,
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { EmailField } from '@payloadcms/ui'
import type { EmailFieldServerComponent } from 'payload'
export const CustomEmailFieldServer: EmailFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { EmailField } from '@payloadcms/ui'
import type { EmailFieldClientComponent } from 'payload'
export const CustomEmailFieldClient: EmailFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { EmailFieldLabelServerComponent } from 'payload'
export const CustomEmailFieldLabelServer: EmailFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { EmailFieldLabelClientComponent } from 'payload'
export const CustomEmailFieldLabelClient: EmailFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Group Field
Source: https://payloadcms.com/docs/fields/group
The Group Field allows [Fields](./overview) to be nested under a common property name. It also groups fields together visually in the [Admin Panel](../admin/overview).
To add a Group Field, set the `type` to `group` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
// highlight-start
type: 'group',
fields: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** | To be used as the property name when stored and retrieved from the database. [More details](../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. Defaults to the field name, if defined. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../graphql/graphql-schema#custom-field-schemas). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Group Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Group Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`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
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageMeta',
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,
},
],
},
],
}
```
## Presentational group fields
You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields.
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
label: 'Page meta',
type: 'group', // required
fields: [
{
name: 'title',
type: 'text',
required: true,
minLength: 20,
maxLength: 100,
},
{
name: 'description',
type: 'textarea',
required: true,
minLength: 40,
maxLength: 160,
},
],
},
],
}
```
# Number Field
Source: https://payloadcms.com/docs/fields/number
The Number Field stores and validates numeric entry and supports additional numerical validation and formatting features.
To add a Number Field, set the `type` to `number` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
type: 'number', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Number Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Number Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------ | --------------------------------------------------------------------------------- |
| **`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
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'age', // required
type: 'number', // required
required: true,
admin: {
step: 1,
},
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { NumberField } from '@payloadcms/ui'
import type { NumberFieldServerComponent } from 'payload'
export const CustomNumberFieldServer: NumberFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { NumberField } from '@payloadcms/ui'
import type { NumberFieldClientComponent } from 'payload'
export const CustomNumberFieldClient: NumberFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { NumberFieldLabelServerComponent } from 'payload'
export const CustomNumberFieldLabelServer: NumberFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { NumberFieldLabelClientComponent } from 'payload'
export const CustomNumberFieldLabelClient: NumberFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Point Field
Source: https://payloadcms.com/docs/fields/point
The Point Field saves a pair of coordinates in the database and assigns an index for location related queries. The data structure in the database matches the GeoJSON structure to represent point. The Payload API simplifies the object data to only the [longitude, latitude] location.
To add a Point Field, set the `type` to `point` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyPointField: Field = {
// ...
type: 'point', // highlight-line
}
```
**Important:** The Point Field currently is not supported in SQLite.
## Config
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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](../database/indexes) 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 details](../fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Example
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'location',
type: 'point',
label: 'Location',
},
],
}
```
## Querying - near
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.
## Querying - within
In order to do query based on whether points are within a specific area defined in GeoJSON, you can use the `within` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: 'points',
where: {
point: {
within: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```
## Querying - intersects
In order to do query based on whether points intersect a specific area defined in GeoJSON, you can use the `intersects` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: 'points',
where: {
point: {
intersects: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { PointField } from '@payloadcms/ui'
import type { PointFieldServerComponent } from 'payload'
export const CustomPointFieldServer: PointFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PointField } from '@payloadcms/ui'
import type { PointFieldClientComponent } from 'payload'
export const CustomPointFieldClient: PointFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { PointFieldLabelServerComponent } from 'payload'
export const CustomPointFieldLabelServer: PointFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { PointFieldLabelClientComponent } from 'payload'
export const CustomPointFieldLabelClient: PointFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Radio Group Field
Source: https://payloadcms.com/docs/fields/radio
The Radio Field 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](../admin/overview).
To add a Radio Field, set the `type` to `radio` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
// highlight-start
type: 'radio',
options: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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. |
| **`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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](../database/postgres)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../graphql/graphql-schema#custom-field-schemas). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
**Important:**
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.
## Admin Options
To customize the appearance and behavior of the Radio Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Radio Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| **`layout`** | Allows for the radio group to be styled as a horizontally or vertically distributed list. The default value is `horizontal`. |
## Example
```ts
import type { CollectionConfig } from 'payload'
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',
},
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { RadioGroupField } from '@payloadcms/ui'
import type { RadioFieldServerComponent } from 'payload'
export const CustomRadioFieldServer: RadioFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { RadioGroupField } from '@payloadcms/ui'
import type { RadioFieldClientComponent } from 'payload'
export const CustomRadioFieldClient: RadioFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RadioFieldLabelServerComponent } from 'payload'
export const CustomRadioFieldLabelServer: RadioFieldLabelServerComponent = ({
clientField,
path,
required,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RadioFieldLabelClientComponent } from 'payload'
export const CustomRadioFieldLabelClient: RadioFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Relationship Field
Source: https://payloadcms.com/docs/fields/relationship
The Relationship Field is one of the most powerful fields Payload features. It provides the ability to easily relate documents together.
The Relationship field is used in a variety of ways, including:
- 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
To add a Relationship Field, set the `type` to `relationship` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
// highlight-start
type: 'relationship',
relationTo: 'products',
// highlight-end
}
```
## Config Options
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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 details](#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 maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](../queries/depth#max-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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to link the field with a relationship. See [Virtual Field Configuration](../fields/overview#virtual-field-configuration) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](../graphql/overview#field-complexity) |
_\* An asterisk denotes that a property is required._
**Tip:** The [Depth](../queries/depth) parameter can be used to automatically
populate related documents that are returned by the API.
## Admin Options
To the appearance and behavior of the Relationship Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Relationship Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More details](#sort-options) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
### Sort Options
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](../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 |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blockData` | The data of the nearest parent block. Will be `undefined` if the field is not within a block or when called on a `Filter` component within the list view. |
| `data` | An object containing the full collection or global document currently being edited. Will be an empty object when called on a `Filter` component within the list view. |
| `id` | The `id` of the current document being edited. Will be `undefined` during the `create` operation or when called on a `Filter` component within the list view. |
| `relationTo` | The collection `slug` to filter against, limited to this field's `relationTo` property. |
| `req` | The Payload Request, which contains references to `payload`, `user`, `locale`, and more. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. Will be an empty object when called on a `Filter` component within the list view. |
| `user` | An object containing the currently authenticated user. |
## Example
```ts
import type { CollectionConfig } from 'payload'
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](../queries/overview).
**Note:**
When a relationship field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default relationship field validation function imported from
**payload/shared** in your validate function.
## Bi-directional relationships
The `relationship` field on its own is used to define relationships for the document that contains the relationship field, and this can be considered as a "one-way" relationship. For example, if you have a Post that has a `category` relationship field on it, the related `category` itself will not surface any information about the posts that have the category set.
However, the `relationship` field can be used in conjunction with the `Join` field to produce powerful bi-directional relationship authoring capabilities. If you're interested in bi-directional relationships, check out the [documentation for the Join field](./join).
## 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 to the `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.
**Note:**
You **cannot** query on a field within a polymorphic relationship as you would with a
non-polymorphic relationship.
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldServerComponent } from 'payload'
export const CustomRelationshipFieldServer: RelationshipFieldServerComponent =
({ clientField, path, schemaPath, permissions }) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldClientComponent } from 'payload'
export const CustomRelationshipFieldClient: RelationshipFieldClientComponent = (
props,
) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelServerComponent } from 'payload'
export const CustomRelationshipFieldLabelServer: RelationshipFieldLabelServerComponent =
(clientField, path) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelClientComponent } from 'payload'
export const CustomRelationshipFieldLabelClient: RelationshipFieldLabelClientComponent =
({ field, path }) => {
return (
)
}
```
# Join Field
Source: https://payloadcms.com/docs/fields/join
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can
edit and view collections
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is
stored on the collection with a Join
field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's
APIs.
The Join field is useful in scenarios including:
- To surface `Orders` for a given `Product`
- To view and edit `Posts` belonging to a `Category`
- To work with any bi-directional relationship data
- Displaying where a document or upload is used in other documents
For the Join field to work, you must have an existing [relationship](./relationship) or [upload](./upload) field in the
collection you are joining. This will reference the collection and path of the field of the related documents.
To add a Relationship Field, set the `type` to `join` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyJoinField: Field = {
// highlight-start
name: 'relatedPosts',
type: 'join',
collection: 'posts',
on: 'category',
// highlight-end
}
// relationship field in another collection:
export const MyRelationshipField: Field = {
name: 'category',
type: 'relationship',
relationTo: 'categories',
}
```
In this example, the field is defined to show the related `posts` when added to a `category` collection. The `on`
property is used to specify the relationship field name of the field that relates to the collection document.
With this example, if you navigate to a Category in the Admin UI or an API response, you'll now see that the Posts which
are related to the Category are populated for you. This is extremely powerful and can be used to define a wide variety
of relationship types in an easy manner.
The Join field is extremely performant and does not add additional query
overhead to your API responses until you add depth of 1 or above. It works in
all database adapters. In MongoDB, we use **aggregations** to automatically
join in related documents, and in relational databases, we use joins.
The Join Field is not supported in
[DocumentDB](https://aws.amazon.com/documentdb/) and [Azure Cosmos
DB](https://azure.microsoft.com/en-us/products/cosmos-db), as we internally
use MongoDB aggregations to query data for that field, which are limited
there. This can be changed in the future.
### Schema advice
When modeling your database, you might come across many places where you'd like to feature bi-directional relationships.
But here's an important consideration—you generally only want to store information about a given relationship in _one_
place.
Let's take the Posts and Categories example. It makes sense to define which category a post belongs to while editing the
post.
It would generally not be necessary to have a list of post IDs stored directly on the category as well, for a few
reasons:
- You want to have a "single source of truth" for relationships, and not worry about keeping two sources in sync with
one another
- If you have hundreds, thousands, or even millions of posts, you would not want to store all of those post IDs on a
given category
- Etc.
This is where the `join` field is especially powerful. With it, you only need to store the `category_id` on the `post`,
and Payload will automatically join in related posts for you when you query for categories. The related category is only
stored on the post itself - and is not duplicated on both sides. However, the `join` field is what enables
bi-directional APIs and UI for you.
### Using the Join field to have full control of your database schema
For typical polymorphic / many relationships, if you're using Postgres or SQLite, Payload will automatically create
a `posts_rels` table, which acts as a junction table to store all of a given document's relationships.
However, this might not be appropriate for your use case if you'd like to have more control over your database
architecture. You might not want to have that `_rels` table, and would prefer to maintain / control your own junction
table design.
With the Join field, you can control your own junction table design, and avoid
Payload's automatic _rels table creation.
The `join` field can be used in conjunction with _any_ collection - and if you wanted to define your own "junction"
collection, which, say, is called `categories_posts` and has a `post_id` and a `category_id` column, you can achieve
complete control over the shape of that junction table.
You could go a step further and leverage the `admin.hidden` property of the `categories_posts` collection to hide the
collection from appearing in the Admin UI navigation.
#### Specifying additional fields on relationships
Another very powerful use case of the `join` field is to be able to define "context" fields on your relationships. Let's
say that you have Posts and Categories, and use join fields on both your Posts and Categories collection to join in
related docs from a new pseudo-junction collection called `categories_posts`. Now, the relations are stored in this
third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add
additional "context" fields to this shared junction collection.
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add
custom "context" fields like `featured` or `spotlight`,
which would allow you to store additional information directly on relationships.
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a
powerful Admin UI.
## Config Options
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when retrieved from the database. [More details](./overview#field-names). |
| **`collection`** \* | The `slug`s having the relationship field or an array of collection slugs. |
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. If `collection` is an array, this field must exist for all specified collections |
| **`orderable`** | If true, enables custom ordering and joined documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| **`where`** | A `Where` query to hide related documents from appearing. Will be merged with any `where` specified in the request. |
| **`maxDepth`** | Default is 1, Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](../queries/depth#max-depth). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
| **`admin`** | Admin-specific configuration. [More details](#admin-config-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](../graphql/overview#field-complexity) |
_\* An asterisk denotes that a property is required._
## Admin Config Options
You can control the user experience of the join field using the `admin` config properties. The following options are supported:
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`defaultColumns`** | Array of field names that correspond to which columns to show in the relationship table. Default is the collection config. |
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
| **`components.Label`** | Override the default Label of the Field Component. [More details](./overview#label) |
| **`disableRowTypes`** | Set to `false` to render row types, and `true` to hide them. Defaults to `false` for join fields with a singular `relationTo`, and `true` for join fields where `relationTo` is an array. |
## Join Field Data
When a document is returned that for a Join field is populated with related documents. The structure returned is an
object with:
- `docs` an array of related documents or only IDs if the depth is reached
- `hasNextPage` a boolean indicating if there are additional documents
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
```json
{
"id": "66e3431a3f23e684075aae9c",
"relatedPosts": {
"docs": [
{
"id": "66e3431a3f23e684075aaeb9",
// other fields...
"category": "66e3431a3f23e684075aae9c"
}
// { ... }
],
"hasNextPage": false,
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
```
## Join Field Data (polymorphic)
When a document is returned that for a polymorphic Join field (with `collection` as an array) is populated with related documents. The structure returned is an
object with:
- `docs` an array of `relationTo` - the collection slug of the document and `value` - the document itself or the ID if the depth is reached
- `hasNextPage` a boolean indicating if there are additional documents
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
```json
{
"id": "66e3431a3f23e684075aae9c",
"relatedPosts": {
"docs": [
{
"relationTo": "posts",
"value": {
"id": "66e3431a3f23e684075aaeb9",
// other fields...
"category": "66e3431a3f23e684075aae9c"
}
}
// { ... }
],
"hasNextPage": false,
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
```
## Query Options
The Join Field supports custom queries to filter, sort, and limit the related documents that will be returned. In
addition to the specific query options for each Join Field, you can pass `joins: false` to disable all Join Field from
returning. This is useful for performance reasons when you don't need the related documents.
The following query options are supported:
| Property | Description |
| ----------- | --------------------------------------------------------------------------------------------------- |
| **`limit`** | The maximum related documents to be returned, default is 10. |
| **`where`** | An optional `Where` query to filter joined documents. Will be merged with the field `where` object. |
| **`sort`** | A string used to order related results |
| **`count`** | Whether include the count of related documents or not. Not included by default |
These can be applied to the Local API, GraphQL, and REST API.
### Local API
By adding `joins` to the Local API you can customize the request for each join field by the `name` of the field.
```js
const result = await payload.find({
collection: 'categories',
where: {
title: {
equals: 'My Category',
},
},
joins: {
relatedPosts: {
limit: 5,
where: {
title: {
equals: 'My Post',
},
},
sort: 'title',
},
},
})
```
Currently, `Where` query support on joined documents for join fields with an
array of `collection` is limited and not supported for fields inside arrays
and blocks.
### Rest API
The REST API supports the same query options as the Local API. You can use the `joins` query parameter to customize the
request for each join field by the `name` of the field. For example, an API call to get a document with the related
posts limited to 5 and sorted by title:
`/api/categories/${id}?joins[relatedPosts][limit]=5&joins[relatedPosts][sort]=title`
You can specify as many `joins` parameters as needed for the same or different join fields for a single request.
### GraphQL
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each
join field in your query.
Example:
```graphql
query {
Categories {
docs {
relatedPosts(
sort: "createdAt"
limit: 5
where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
"""
Optionally pass count: true if you want to retrieve totalDocs
"""
count: true -- s
) {
docs {
title
}
hasNextPage
totalDocs
}
}
}
}
```
# Rich Text Field
Source: https://payloadcms.com/docs/fields/rich-text
The Rich Text Field lets editors write and format dynamic content in a familiar interface. The content is saved as JSON in the database and can be converted to HTML or any other format needed.
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 Payload rich text editor.
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.
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](./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 details](./overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](./overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`editor`** | Customize or override the rich text editor. [More details](../rich-text/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
\*_ An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option. The Rich Text Field inherits all the default options from the base [Field Admin Config](./overview#admin-options).
```ts
import type { Field } from 'payload'
export const MyRichTextField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
Further customization can be done with editor-specific options.
## 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 the [rich text editor documentation](../rich-text/overview).
# Row Field
Source: https://payloadcms.com/docs/fields/row
The Row Field is presentational-only and only affects the [Admin Panel](../admin/overview). By using it, you can arrange [Fields](./overview) next to each other horizontally.
To add a Row Field, set the `type` to `row` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyRowField: Field = {
// ...
// highlight-start
type: 'row',
fields: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **`fields`** \* | Array of field types to nest within this Row. |
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
## Example
```ts
import type { CollectionConfig } from 'payload'
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%',
},
},
],
},
],
}
```
# Select Field
Source: https://payloadcms.com/docs/fields/select
The Select Field provides a dropdown-style interface for choosing options from a predefined list as an enumeration.
To add a Select Field, set the `type` to `select` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
// highlight-start
type: 'select',
options: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](../fields/overview#admin-options) 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](../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](../database/postgres)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../graphql/graphql-schema#custom-field-schemas). |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filteroptions) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
**Important:** 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.
### filterOptions
Used to dynamically filter which options are available based on the current user, document data, or other criteria.
Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g. a city/state selector
The result of `filterOptions` will determine:
- Which options are displayed in the Admin Panel
- Which options can be saved to the database
To do this, use the `filterOptions` property in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
// highlight-start
type: 'select',
options: [
{
label: 'One',
value: 'one',
},
{
label: 'Two',
value: 'two',
},
{
label: 'Three',
value: 'three',
},
],
filterOptions: ({ options, data }) =>
data.disallowOption1
? options.filter(
(option) =>
(typeof option === 'string' ? options : option.value) !== 'one',
)
: options,
// highlight-end
}
```
**Note:** This property is similar to `filterOptions` in
[Relationship](./relationship) or [Upload](./upload) fields, except that the
return value of this function is simply an array of options, not a query
constraint.
## Admin Options
To customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Select Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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`) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
## Example
```ts
import type { CollectionConfig } from 'payload'
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',
},
],
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type { SelectFieldServerComponent } from 'payload'
import type React from 'react'
import { SelectField } from '@payloadcms/ui'
export const CustomSelectFieldServer: SelectFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import type { SelectFieldClientComponent } from 'payload'
import { SelectField } from '@payloadcms/ui'
import React from 'react'
export const CustomSelectFieldClient: SelectFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { SelectFieldLabelServerComponent } from 'payload'
export const CustomSelectFieldLabelServer: SelectFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { SelectFieldLabelClientComponent } from 'payload'
export const CustomSelectFieldLabelClient: SelectFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
# Tabs Field
Source: https://payloadcms.com/docs/fields/tabs
The Tabs Field is presentational-only and only affects the [Admin Panel](../admin/overview) (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.
To add a Tabs Field, set the `type` to `tabs` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyTabsField: Field = {
// ...
// highlight-start
type: 'tabs',
tabs: [
// ...
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ------------- | ----------------------------------------------------------------------- |
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |
| **`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 details](../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](../typescript/generating-types#custom-field-interfaces) & [GraphQL type](../graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Example
```ts
import type { CollectionConfig } from 'payload'
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,
},
],
},
],
},
],
}
```
# Text Field
Source: https://payloadcms.com/docs/fields/text
The Text Field is one of the most commonly used fields. It saves a string to the database and provides the [Admin Panel](../admin/overview) with a simple text input.
To add a Text Field, set the `type` to `text` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
type: 'text', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`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. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Text Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| **`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
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageTitle', // required
type: 'text', // required
required: true,
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { TextField } from '@payloadcms/ui'
import type { TextFieldServerComponent } from 'payload'
export const CustomTextFieldServer: TextFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { TextField } from '@payloadcms/ui'
import type { TextFieldClientComponent } from 'payload'
export const CustomTextFieldClient: TextFieldClientComponent = (props) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextFieldLabelServerComponent } from 'payload'
export const CustomTextFieldLabelServer: TextFieldLabelServerComponent = ({
clientField,
path,
required,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextFieldLabelClientComponent } from 'payload'
export const CustomTextFieldLabelClient: TextFieldLabelClientComponent = ({
field,
path,
}) => {
return (
)
}
```
## Slug Field
The slug field is experimental and may change, or even be removed, in future
releases. Use at your own risk.
One common use case for the Text Field is to create a "slug" for a document. A slug is a unique, indexed, URL-friendly string that identifies a particular document, often used to construct the URL of a webpage.
Payload provides a built-in Slug Field so you don't have to built one from scratch. This field automatically generates a slug based on the value of another field, such as a title or name field. It provides UI to lock and unlock the field to protect its value, as well as to re-generate the slug on-demand.
To add a Slug Field, import the `slugField` into your field schema:
```ts
import { slugField } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
// ...
fields: [
// ...
// highlight-line
slugField(),
// highlight-line
],
}
```
The slug field exposes a few top-level config options for easy customization:
| Option | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | To be used as the slug field's name. Defaults to `slug`. |
| `overrides` | A function that receives the default fields so you can override on a granular level. See example below. [More details](#slug-overrides). |
| `checkboxName` | To be used as the name for the `generateSlug` checkbox field. Defaults to `generateSlug`. |
| `fieldToUse` | The name of the field to use when generating the slug. This field must exist in the same collection. Defaults to `title`. |
| `localized` | Enable localization on the `slug` and `generateSlug` fields. Defaults to `false`. |
| `position` | The position of the slug field. [More details](./overview#admin-options). |
| `required` | Require the slug field. Defaults to `true`. |
### Slug Overrides
If the above options aren't sufficient for your use case, you can use the `overrides` function to customize the slug field at a granular level. The `overrides` function receives the default fields that make up the slug field, and you can modify them to any extent you need.
```ts
import { slugField } from 'payload'
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
// ...
fields: [
// ...
// highlight-line
slugField({
overrides: (defaultField) => {
defaultField.fields[1].label = 'Custom Slug Label'
return defaultField
},
}),
// highlight-line
],
}
```
# Textarea Field
Source: https://payloadcms.com/docs/fields/textarea
The Textarea Field is nearly identical to the [Text Field](./text) but it features a slightly larger input that is better suited to edit longer text.
To add a Textarea Field, set the `type` to `textarea` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
type: 'textarea', // highlight-line
}
```
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_\* An asterisk denotes that a property is required._
## Admin Options
To customize the appearance and behavior of the Textarea Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The Textarea Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
| **`rows`** | Set the number of visible text rows in the textarea. Defaults to `2` if not specified. |
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
## Example
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'metaDescription', // required
type: 'textarea', // required
required: true,
},
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { TextareaField } from '@payloadcms/ui'
import type { TextareaFieldServerComponent } from 'payload'
export const CustomTextareaFieldServer: TextareaFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { TextareaField } from '@payloadcms/ui'
import type { TextareaFieldClientComponent } from 'payload'
export const CustomTextareaFieldClient: TextareaFieldClientComponent = (
props,
) => {
return
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextareaFieldLabelServerComponent } from 'payload'
export const CustomTextareaFieldLabelServer: TextareaFieldLabelServerComponent =
({ clientField, path }) => {
return (
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextareaFieldLabelClientComponent } from 'payload'
export const CustomTextareaFieldLabelClient: TextareaFieldLabelClientComponent =
({ field, path }) => {
return (
)
}
```
# UI Field
Source: https://payloadcms.com/docs/fields/ui
The UI (user interface) Field gives you a ton of power to add your own React components directly into the [Admin Panel](../admin/overview), nested directly within your other fields. It has absolutely no effect on the data of your documents. It is presentational-only. 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 the UI Field, you can:
- 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
To add a UI Field, set the `type` to `ui` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyUIField: Field = {
// ...
type: 'ui', // highlight-line
}
```
## Config Options
| 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 details](./overview#field). |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More details](./overview#cell). |
| **`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
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'myCustomUIField', // required
type: 'ui', // required
admin: {
components: {
Field: '/path/to/MyCustomUIField',
Cell: '/path/to/MyCustomUICell',
},
},
},
],
}
```
# Upload Field
Source: https://payloadcms.com/docs/fields/upload
The Upload Field allows for the selection of a Document from a Collection supporting [Uploads](../upload/overview), and
formats the selection as a thumbnail in the Admin Panel.
Upload fields are useful for a variety of use cases, such as:
- 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
To create an Upload Field, set the `type` to `upload` in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MyUploadField: Field = {
// ...
// highlight-start
type: 'upload',
relationTo: 'media',
// highlight-end
}
```
**Important:** To use the Upload Field, you must have a
[Collection](../configuration/collections) configured to allow
[Uploads](../upload/overview).
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](../fields/overview#field-names). |
| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More details](#filtering-upload-options). |
| **`hasMany`** | Boolean which, 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](../queries/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 details](../fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) 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](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`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 details](../fields/overview#default-values). |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More details](../upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [Admin Options](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](../fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](../graphql/overview#field-complexity) |
_\* An asterisk denotes that a property is required._
## Example
```ts
import type { CollectionConfig } from 'payload'
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](../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 collection `slug` to filter against, limited to this field's `relationTo` property |
| `data` | An object containing the full collection or global document currently being edited |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation |
| `user` | An object containing the currently authenticated user |
| `req` | The Payload Request, which contains references to `payload`, `user`, `locale`, and more. |
### Example#filter-options-example
```ts
const uploadField = {
name: 'image',
type: 'upload',
relationTo: 'media',
filterOptions: {
mimeType: { contains: 'image' },
},
}
```
You can learn more about writing queries [here](../queries/overview).
**Note:**
When an upload field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default upload field validation function imported from
**payload/shared** in your validate function.
## Bi-directional relationships
The `upload` field on its own is used to reference documents in an upload collection. This can be considered a "one-way"
relationship. If you wish to allow an editor to visit the upload document and see where it is being used, you may use
the `join` field in the upload enabled collection. Read more about bi-directional relationships using
the [Join field](./join)
# Access Control
Source: https://payloadcms.com/docs/access-control/overview
Access Control determines what a user can and cannot do with any given Document, as well as what they can and cannot see within the [Admin Panel](../admin/overview). By implementing Access Control, you can define granular restrictions based on the user, their roles (RBAC), Document data, or any other criteria your application requires.
Access Control functions are scoped to the _operation_, meaning you can have different rules for `create`, `read`, `update`, `delete`, etc. Access Control functions are executed _before_ any changes are made and _before_ any operations are completed. This allows you to determine if the user has the necessary permissions before fulfilling the request.
There are many use cases for Access Control, including:
- Allowing anyone `read` access to all posts
- Only allowing public access to posts where a `status` field is equal to `published`
- Giving only users with a `role` field equal to `admin` the ability to delete posts
- Allowing anyone to submit contact forms, but only logged in users to `read`, `update` or `delete` them
- Restricting a user to only be able to see their own orders, but no-one else's
- Allowing users that belong to a certain organization to access only that organization's resources
There are three main types of Access Control in Payload:
- [Collection Access Control](./collections)
- [Global Access Control](./globals)
- [Field Access Control](./fields)
## Default Access Control
Payload provides default Access Control so that your data is secured behind [Authentication](../authentication/overview) without additional configuration. To do this, Payload sets a default function that simply checks if a user is present on the request. You can override this default behavior by defining your own Access Control functions as needed.
Here is the default Access Control that Payload provides:
```ts
const defaultPayloadAccess = ({ req: { user } }) => {
// Return `true` if a user is found
// and `false` if it is undefined or null
return Boolean(user) // highlight-line
}
```
**Important:** In the [Local API](../local-api/overview), all Access Control
is _skipped_ by default. This allows your server to have full control over
your application. To opt back in, you can set the `overrideAccess` option to
`false` in your requests.
## The Access Operation
The Admin Panel responds dynamically to your changes to Access Control. For example, if you restrict editing `ExampleCollection` to only users that feature an "admin" role, Payload will **hide** that Collection from the Admin Panel entirely. This is super powerful and allows you to control who can do what within your Admin Panel using the same functions that secure your APIs.
To accomplish this, Payload exposes the [Access Operation](../authentication/operations#access). Upon login, Payload executes each Access Control function at the top level, across all Collections, Globals, and Fields, and returns a response that contains a reflection of what the currently authenticated user can do within your application.
**Important:** When your access control functions are executed via the [Access
Operation](../authentication/operations#access), the `id` and `data` arguments
will be `undefined`. This is because Payload is executing your functions
without referencing a specific Document.
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 Panel.
## Locale Specific Access Control
To implement locale-specific access control, you can use the `req.locale` argument in your access control functions. This argument allows you to evaluate the current locale of the request and determine access permissions accordingly.
Here is an example:
```ts
const access = ({ req }) => {
// Grant access if the locale is 'en'
if (req.locale === 'en') {
return true
}
// Deny access for all other locales
return false
}
```
# Collection Access Control
Source: https://payloadcms.com/docs/access-control/collections
Collection Access Control is [Access Control](../access-control/overview) used to restrict access to Documents within a [Collection](../getting-started/concepts#collections), as well as what they can and cannot see within the [Admin Panel](../admin/overview) as it relates to that Collection.
To add Access Control to a Collection, use the `access` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithAccessControl: CollectionConfig = {
// ...
access: {
// highlight-line
// ...
},
}
```
## Config Options
Access Control is specific to the operation of the request.
To add Access Control to a Collection, use the `access` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload';
export const CollectionWithAccessControl: CollectionConfig = {
// ...
// highlight-start
access: {
create: () => {...},
read: () => {...},
update: () => {...},
delete: () => {...},
// Auth-enabled Collections only
admin: () => {...},
unlock: () => {...},
// Version-enabled Collections only
readVersions: () => {...},
},
// highlight-end
}
```
The following options are available:
| Function | Allows/Denies Access |
| ------------ | -------------------------------------------------------------------- |
| **`create`** | Used in the `create` operation. [More details](#create). |
| **`read`** | Used in the `find` and `findByID` operations. [More details](#read). |
| **`update`** | Used in the `update` operation. [More details](#update). |
| **`delete`** | Used in the `delete` operation. [More details](#delete). |
If a Collection supports [`Authentication`](../authentication/overview), the following additional options are available:
| Function | Allows/Denies Access |
| ------------ | ---------------------------------------------------------------------------------------- |
| **`admin`** | Used to restrict access to the [Admin Panel](../admin/overview). [More details](#admin). |
| **`unlock`** | Used to restrict which users can access the `unlock` operation. [More details](#unlock). |
If a Collection supports [Versions](../versions/overview), the following additional options are available:
| 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. [More details](#read-versions). |
### Create
Returns a boolean which allows/denies access to the `create` request.
To add create Access Control to a Collection, use the `create` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithCreateAccess: CollectionConfig = {
// ...
access: {
// highlight-start
create: ({ req: { user }, data }) => {
return Boolean(user)
},
// highlight-end
},
}
```
The following arguments are provided to the `create` function:
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`data`** | The data passed to create the document with. |
### Read
Returns a boolean which allows/denies access to the `read` request.
To add read Access Control to a Collection, use the `read` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithReadAccess: CollectionConfig = {
// ...
access: {
// highlight-start
read: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
**Tip:** Return a [Query](../queries/overview) to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. [More details](../queries/overview).
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
```ts
import type { Access } from 'payload'
export const canReadPage: Access = ({ req: { user } }) => {
// Allow authenticated users
if (user) {
return true
}
// By returning a Query, guest users can read public Documents
// Note: this assumes you have a `isPublic` checkbox field on your Collection
return {
isPublic: {
equals: true,
},
}
}
```
The following arguments are provided to the `read` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`id`** | `id` of document requested, if within `findByID`. |
### Update
Returns a boolean which allows/denies access to the `update` request.
To add update Access Control to a Collection, use the `update` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithUpdateAccess: CollectionConfig = {
// ...
access: {
// highlight-start
update: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
**Tip:** Return a [Query](../queries/overview) to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. [More details](../queries/overview).
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
```ts
import type { Access } from 'payload'
export 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
}
```
The following arguments are provided to the `update` function:
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`id`** | `id` of document requested to update. |
| **`data`** | The data passed to update the document with. |
### Delete
Similarly to the Update function, returns a boolean or a [query constraint](../queries/overview) to limit which documents can be deleted by which users.
To add delete Access Control to a Collection, use the `delete` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithDeleteAccess: CollectionConfig = {
// ...
access: {
// highlight-start
delete: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
```ts
import type { Access } from 'payload'
export 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
}
```
The following arguments are provided to the `delete` function:
| Option | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object with additional `user` property, which is the currently logged in user. |
| **`id`** | `id` of document requested to delete. |
### Admin
If the Collection is used to access the [Admin Panel](../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.
To add Admin Access Control to a Collection, use the `admin` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithAdminAccess: CollectionConfig = {
// ...
access: {
// highlight-start
admin: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
The following arguments are provided to the `admin` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Unlock
Determines which users can [unlock](../authentication/operations#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](../authentication/overview#config-options).
To add Unlock Access Control to a Collection, use the `unlock` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithUnlockAccess: CollectionConfig = {
// ...
access: {
// highlight-start
unlock: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
The following arguments are provided to the `unlock` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Read Versions
If the Collection has [Versions](../versions/overview) enabled, the `readVersions` Access Control function determines whether or not the currently logged in user can access the version history of a Document.
To add Read Versions Access Control to a Collection, use the `readVersions` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithVersionsAccess: CollectionConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
The following arguments are provided to the `readVersions` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
# Globals Access Control
Source: https://payloadcms.com/docs/access-control/globals
Global Access Control is [Access Control](../access-control/overview) used to restrict access to [Global](../configuration/globals) Documents, as well as what they can and cannot see within the [Admin Panel](../admin/overview) as it relates to that Global.
To add Access Control to a Global, use the `access` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload'
export const GlobalWithAccessControl: GlobalConfig = {
// ...
access: {
// highlight-line
// ...
},
}
```
## Config Options
Access Control is specific to the operation of the request.
To add Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
const GlobalWithAccessControl: GlobalConfig = {
// ...
// highlight-start
access: {
read: ({ req: { user } }) => {...},
update: ({ req: { user } }) => {...},
// Version-enabled Globals only
readVersions: () => {...},
},
// highlight-end
}
export default Header
```
The following options are available:
| Function | Allows/Denies Access |
| ------------ | --------------------------------------------------------------- |
| **`read`** | Used in the `findOne` Global operation. [More details](#read). |
| **`update`** | Used in the `update` Global operation. [More details](#update). |
If a Global supports [Versions](../versions/overview), the following additional options are available:
| 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. [More details](#read-versions). |
### Read
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
const Header: GlobalConfig = {
// ...
// highlight-start
access: {
read: ({ req: { user } }) => {
return Boolean(user)
},
},
// highlight-end
}
```
The following arguments are provided to the `read` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Update
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can update this global based on its current properties.
To add update Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
const Header: GlobalConfig = {
// ...
// highlight-start
access: {
update: ({ req: { user }, data }) => {
return Boolean(user)
},
},
// highlight-end
}
```
The following arguments are provided to the `update` function:
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`data`** | The data passed to update the global with. |
### Read Versions
If the Global has [Versions](../versions/overview) enabled, the `readVersions` Access Control function determines whether or not the currently logged in user can access the version history of a Document.
To add Read Versions Access Control to a Collection, use the `readVersions` property in the [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload'
export const GlobalWithVersionsAccess: GlobalConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
The following arguments are provided to the `readVersions` function:
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
# Field-level Access Control
Source: https://payloadcms.com/docs/access-control/fields
Field Access Control is [Access Control](../access-control/overview) used to restrict access to specific [Fields](../fields/overview) within a Document.
To add Access Control to a Field, use the `access` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const FieldWithAccessControl: Field = {
// ...
access: {
// highlight-line
// ...
},
}
```
**Note:** Field Access Controls does not support returning
[Query](../queries/overview) constraints like [Collection Access
Control](./collections) does.
## Config Options
Access Control is specific to the operation of the request.
To add Access Control to a Field, use the `access` property in the [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload';
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
};
],
};
```
The following options are available:
| Function | Purpose |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| **`create`** | Allows or denies the ability to set a field's value when creating a new document. [More details](#create). |
| **`read`** | Allows or denies the ability to read a field's value. [More details](#read). |
| **`update`** | Allows or denies the ability to update a field's value [More details](#update). |
### 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 [Request](https://developer.mozilla.org/en-US/docs/Web/API/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 [Request](https://developer.mozilla.org/en-US/docs/Web/API/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 [Request](https://developer.mozilla.org/en-US/docs/Web/API/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. |
# Hooks Overview
Source: https://payloadcms.com/docs/hooks/overview
Hooks allow you to execute your own side effects during specific events of the Document lifecycle. They allow you to do things like mutate data, perform business logic, integrate with third-parties, or anything else, all during precise moments within your application.
With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework. There are many use cases for Hooks, including:
- Modify data before it is read or updated
- Encrypt and decrypt sensitive data
- Integrate with a third-party CRM like HubSpot or Salesforce
- Send a copy of uploaded files to Amazon S3 or similar
- Process orders through a payment provider like Stripe
- Send emails when contact forms are submitted
- Track data ownership or changes over time
There are four main types of Hooks in Payload:
- [Root Hooks](#root-hooks)
- [Collection Hooks](../hooks/collections)
- [Global Hooks](../hooks/globals)
- [Field Hooks](../hooks/fields)
**Reminder:** Payload also ships a set of _React_ hooks that you can use in
your frontend application. Although they share a common name, these are very
different things and should not be confused. [More
details](../admin/react-hooks).
## Root Hooks
Root Hooks are not associated with any specific Collection, Global, or Field. They are useful for globally-oriented side effects, such as when an error occurs at the application level.
To add Root Hooks, use the `hooks` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
hooks: {
afterError:[() => {...}]
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| ---------------- | ------------------------------------------------------ |
| **`afterError`** | Runs after an error occurs in the Payload application. |
### afterError
The `afterError` Hook is triggered when an error occurs in the Payload application. This can be useful for logging errors to a third-party service, sending an email to the development team, logging the error to Sentry or DataDog, etc. The output can be used to transform the result object / status code.
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
hooks: {
afterError: [
async ({ error }) => {
// Do something
},
],
},
})
```
The following arguments are provided to the `afterError` Hook:
| Argument | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`error`** | The error that occurred. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`graphqlResult`** | The GraphQL result object, available if the hook is executed within a GraphQL context. |
| **`req`** | The `PayloadRequest` object that extends [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). Contains currently authenticated `user` and the Local API instance `payload`. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. This will be `undefined` if the hook is executed from a non-collection endpoint or GraphQL. |
| **`result`** | The formatted error result object, available if the hook is executed from a REST context. |
## Async vs. Synchronous
All Hooks can be written as either synchronous or asynchronous functions. Choosing the right type depends on your use case, but switching between the two is as simple as adding or removing the `async` keyword.
#### Asynchronous
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. This way 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.
**Tip:** If your hook executes a long-running task that doesn't affect the
response in any way, consider [offloading it to the job
queue](#offloading-long-running-tasks). That will free up the request to
continue processing without waiting for the task to complete.
#### Synchronous
If your Hook simply performs a side-effect, such as mutating document data, 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
Hooks are only triggered on the server and are automatically excluded from the client-side bundle. This means that you can safely use sensitive business logic in your Hooks without worrying about exposing it to the client.
## Performance
Hooks are a powerful way to customize the behavior of your APIs, but some hooks are run very often and can add significant overhead to your requests if not optimized.
When building hooks, combine together as many of these strategies as possible to ensure your hooks are as performant as they can be.
For more performance tips, see the [Performance
documentation](../performance/overview).
### Writing efficient hooks
Consider when hooks are run. One common pitfall is putting expensive logic in hooks that run very often.
For example, the `read` operation runs on every read request, so avoid putting expensive logic in a `beforeRead` or `afterRead` hook.
```ts
{
hooks: {
beforeRead: [
async () => {
// This runs on every read request - avoid expensive logic here
await doSomethingExpensive()
return data
},
],
},
}
```
Instead, you might want to use a `beforeChange` or `afterChange` hook, which only runs when a document is created or updated.
```ts
{
hooks: {
beforeChange: [
async ({ context }) => {
// This is more acceptable here, although still should be mindful of performance
await doSomethingExpensive()
// ...
},
]
},
}
```
### Using Hook Context
Use [Hook Context](./context) avoid prevent infinite loops or avoid repeating expensive operations across multiple hooks in the same request.
```ts
{
hooks: {
beforeChange: [
async ({ context }) => {
const somethingExpensive = await doSomethingExpensive()
context.somethingExpensive = somethingExpensive
// ...
},
],
},
}
```
To learn more, see the [Hook Context documentation](./context).
### Offloading to the jobs queue
If your hooks perform any long-running tasks that don't direct affect request lifecycle, consider offloading them to the [jobs queue](../jobs-queue/overview). This will free up the request to continue processing without waiting for the task to complete.
```ts
{
hooks: {
afterChange: [
async ({ doc, req }) => {
// Offload to job queue
await req.payload.jobs.queue(...)
// ...
},
],
},
}
```
To learn more, see the [Job Queue documentation](../jobs-queue/overview).
# Collection Hooks
Source: https://payloadcms.com/docs/hooks/collections
Collection Hooks are [Hooks](./overview) that run on Documents within a specific [Collection](../configuration/collections). They allow you to execute your own logic during specific events of the Document lifecycle.
To add Hooks to a Collection, use the `hooks` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithHooks: CollectionConfig = {
// ...
hooks: {
// highlight-line
// ...
},
}
```
**Tip:** You can also set hooks on the field-level to isolate hook logic to
specific fields. [More details](./fields).
## Config Options
All Collection Hooks accept an array of [synchronous or asynchronous functions](./overview#async-vs-synchronous). Each Collection Hook receives specific arguments based on its own type, and has the ability to modify specific outputs.
```ts
import type { CollectionConfig } from 'payload';
export const CollectionWithHooks: CollectionConfig = {
// ...
// highlight-start
hooks: {
beforeOperation: [(args) => {...}],
beforeValidate: [(args) => {...}],
beforeDelete: [(args) => {...}],
beforeChange: [(args) => {...}],
beforeRead: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
afterDelete: [(args) => {...}],
afterOperation: [(args) => {...}],
afterError: [(args) => {....}],
// Auth-enabled Hooks
beforeLogin: [(args) => {...}],
afterLogin: [(args) => {...}],
afterLogout: [(args) => {...}],
afterRefresh: [(args) => {...}],
afterMe: [(args) => {...}],
afterForgotPassword: [(args) => {...}],
refresh: [(args) => {...}],
me: [(args) => {...}],
},
// highlight-end
}
```
### beforeOperation
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
```ts
import type { CollectionBeforeOperationHook } from 'payload'
const beforeOperationHook: CollectionBeforeOperationHook = async ({
args,
operation,
req,
}) => {
return args // return modified operation arguments as necessary
}
```
The following arguments are provided to the `beforeOperation` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. Available options include: `autosave`, `count`, `countVersions`, `create`, `delete`, `forgotPassword`, `login`, `read`, `readDistinct`, `refresh`, `resetPassword`, `restoreVersion`, and `update`. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`operation`** | The name of the operation that this hook is running within. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeValidate
Runs during 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 client-side validation. If you render a custom field component in your front-end and provide it with a `validate` function, the order that validations will run in is:
1. `validate` runs on the client
2. if successful, `beforeValidate` runs on the server
3. `validate` runs on the server
```ts
import type { CollectionBeforeValidateHook } from 'payload'
const beforeValidateHook: CollectionBeforeValidateHook = async ({ data }) => {
return data
}
```
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeChange
Immediately before validation, beforeChange hooks will run during create and update operations. At this stage, the data should be treated as unvalidated user input. There is no guarantee that required fields exist or that fields are in the correct format. As such, using this data for side effects requires manual validation. You can optionally modify the shape of the data to be saved.
```ts
import type { CollectionBeforeChangeHook } from 'payload'
const beforeChangeHook: CollectionBeforeChangeHook = async ({ data }) => {
return data
}
```
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { CollectionAfterChangeHook } from 'payload'
const afterChangeHook: CollectionAfterChangeHook = async ({ doc }) => {
return doc
}
```
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { CollectionBeforeReadHook } from 'payload'
const beforeReadHook: CollectionBeforeReadHook = async ({ doc }) => {
return doc
}
```
The following arguments are provided to the `beforeRead` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`query`** | The [Query](../queries/overview) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { CollectionAfterReadHook } from 'payload'
const afterReadHook: CollectionAfterReadHook = async ({ doc }) => {
return doc
}
```
The following arguments are provided to the `afterRead` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`query`** | The [Query](../queries/overview) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeDelete
Runs before the `delete` operation. Returned values are discarded.
```ts
import type { CollectionBeforeDeleteHook } from 'payload';
const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
req,
id,
}) => {...}
```
The following arguments are provided to the `beforeDelete` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`id`** | The ID of the Document being deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterDelete
Runs immediately after the `delete` operation removes records from the database. Returned values are discarded.
```ts
import type { CollectionAfterDeleteHook } from 'payload';
const afterDeleteHook: CollectionAfterDeleteHook = async ({
req,
id,
doc,
}) => {...}
```
The following arguments are provided to the `afterDelete` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`id`** | The ID of the Document that was deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { CollectionAfterOperationHook } from 'payload'
const afterOperationHook: CollectionAfterOperationHook = async ({ result }) => {
return result
}
```
The following arguments are provided to the `afterOperation` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`result`** | The result of the operation, before modifications. |
### afterError
The `afterError` Hook is triggered when an error occurs in the Payload application. This can be useful for logging errors to a third-party service, sending an email to the development team, logging the error to Sentry or DataDog, etc. The output can be used to transform the result object / status code.
```ts
import type { CollectionAfterErrorHook } from 'payload';
const afterErrorHook: CollectionAfterErrorHook = async ({
req,
id,
doc,
}) => {...}
```
The following arguments are provided to the `afterError` Hook:
| Argument | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`error`** | The error that occurred. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`graphqlResult`** | The GraphQL result object, available if the hook is executed within a GraphQL context. |
| **`req`** | The `PayloadRequest` object that extends [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). Contains currently authenticated `user` and the Local API instance `payload`. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`result`** | The formatted error result object, available if the hook is executed from a REST context. |
### beforeLogin
For [Auth-enabled Collections](../authentication/overview), 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 type { CollectionBeforeLoginHook } from 'payload'
const beforeLoginHook: CollectionBeforeLoginHook = async ({ user }) => {
return user
}
```
The following arguments are provided to the `beforeLogin` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`user`** | The user being logged in. |
### afterLogin
For [Auth-enabled Collections](../authentication/overview), this hook runs after successful `login` operations. You can optionally modify the user that is returned.
```ts
import type { CollectionAfterLoginHook } from 'payload';
const afterLoginHook: CollectionAfterLoginHook = async ({
user,
token,
}) => {...}
```
The following arguments are provided to the `afterLogin` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The token generated for the user. |
| **`user`** | The user being logged in. |
### afterLogout
For [Auth-enabled Collections](../authentication/overview), this hook runs after `logout` operations.
```ts
import type { CollectionAfterLogoutHook } from 'payload';
const afterLogoutHook: CollectionAfterLogoutHook = async ({
req,
}) => {...}
```
The following arguments are provided to the `afterLogout` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterMe
For [Auth-enabled Collections](../authentication/overview), this hook runs after `me` operations.
```ts
import type { CollectionAfterMeHook } from 'payload';
const afterMeHook: CollectionAfterMeHook = async ({
req,
response,
}) => {...}
```
The following arguments are provided to the `afterMe` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`response`** | The response to return. |
### afterRefresh
For [Auth-enabled Collections](../authentication/overview), this hook runs after `refresh` operations.
```ts
import type { CollectionAfterRefreshHook } from 'payload';
const afterRefreshHook: CollectionAfterRefreshHook = async ({
token,
}) => {...}
```
The following arguments are provided to the `afterRefresh` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`exp`** | The expiration time of the token. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The newly refreshed user token. |
### afterForgotPassword
For [Auth-enabled Collections](../authentication/overview), this hook runs after successful `forgotPassword` operations. Returned values are discarded.
```ts
import type { CollectionAfterForgotPasswordHook } from 'payload'
const afterForgotPasswordHook: CollectionAfterForgotPasswordHook = async ({
args,
context,
collection,
}) => {...}
```
The following arguments are provided to the `afterForgotPassword` hook:
| Option | Description |
| ---------------- | ------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
### refresh
For [Auth-enabled Collections](../authentication/overview), 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'
const myRefreshHook: CollectionRefreshHook = async ({
args,
user,
}) => {...}
```
The following arguments are provided to the `afterRefresh` hook:
| Option | Description |
| ---------- | ---------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
### me
For [Auth-enabled Collections](../authentication/overview), 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'
const meHook: CollectionMeHook = async ({
args,
user,
}) => {...}
```
The following arguments are provided to the `me` hook:
| Option | Description |
| ---------- | ---------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
## 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'
```
# Global Hooks
Source: https://payloadcms.com/docs/hooks/globals
Global Hooks are [Hooks](./overview) that run on [Global](../configuration/globals) Documents. They allow you to execute your own logic during specific events of the Document lifecycle.
To add Hooks to a Global, use the `hooks` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload'
export const GlobalWithHooks: GlobalConfig = {
// ...
hooks: {
// highlight-line
// ...
},
}
```
**Tip:** You can also set hooks on the field-level to isolate hook logic to
specific fields. [More details](./fields).
## Config Options
All Global Hooks accept an array of [synchronous or asynchronous functions](./overview#async-vs-synchronous). Each Global Hook receives specific arguments based on its own type, and has the ability to modify specific outputs.
```ts
import type { GlobalConfig } from 'payload';
const GlobalWithHooks: GlobalConfig = {
// ...
// highlight-start
hooks: {
beforeOperation: [(args) => {...}],
beforeValidate: [(args) => {...}],
beforeChange: [(args) => {...}],
beforeRead: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
}
// highlight-end
}
```
### beforeOperation
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
```ts
import type { GlobalBeforeOperationHook } from 'payload'
const beforeOperationHook: GlobalBeforeOperationHook = async ({
args,
operation,
req,
}) => {
return args // return modified operation arguments as necessary
}
```
The following arguments are provided to the `beforeOperation` hook:
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. Available operation include: `countVersions`, `read`, `restoreVersion`, and `update`. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`operation`** | The name of the operation that this hook is running within. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeValidate
Runs during the `update` operation. 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 client-side validation. If you render a custom field component in your front-end and provide it with a `validate` function, the order that validations will run in is:
1. `validate` runs on the client
2. if successful, `beforeValidate` runs on the server
3. `validate` runs on the server
```ts
import type { GlobalBeforeValidateHook } from 'payload'
const beforeValidateHook: GlobalBeforeValidateHook = async ({
data,
req,
originalDoc,
}) => {
return data
}
```
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { GlobalBeforeChangeHook } from 'payload'
const beforeChangeHook: GlobalBeforeChangeHook = async ({
data,
req,
originalDoc,
}) => {
return data
}
```
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { GlobalAfterChangeHook } from 'payload'
const afterChangeHook: GlobalAfterChangeHook = async ({
doc,
previousDoc,
req,
}) => {
return data
}
```
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { GlobalBeforeReadHook } from 'payload'
const beforeReadHook: GlobalBeforeReadHook = async ({
doc,
req,
}) => {...}
```
The following arguments are provided to the `beforeRead` hook:
| Option | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### 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 type { GlobalAfterReadHook } from 'payload'
const afterReadHook: GlobalAfterReadHook = async ({
doc,
req,
findMany,
}) => {...}
```
The following arguments are provided to the `beforeRead` hook:
| Option | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many (useful in versions). |
| **`doc`** | The resulting Document after changes are applied. |
| **`query`** | The [Query](../queries/overview) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
## 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'
```
# Field Hooks
Source: https://payloadcms.com/docs/hooks/fields
Field Hooks are [Hooks](./overview) that run on Documents on a per-field basis. They allow you to execute your own logic during specific events of the Document lifecycle. Field Hooks offer incredible potential for isolating your logic from the rest of your [Collection Hooks](./collections) and [Global Hooks](./globals).
To add Hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const FieldWithHooks: Field = {
// ...
hooks: {
// highlight-line
// ...
},
}
```
## Config Options
All Field Hooks accept an array of synchronous or asynchronous functions. These functions can optionally modify the return value of the field before the operation continues. All Field Hooks are formatted to accept the same arguments, although some arguments may be `undefined` based the specific hook type.
**Important:** Due to GraphQL's typed nature, changing the type of data that
you return from a field will produce errors in the [GraphQL
API](../graphql/overview). If you need to change the shape or type of data,
consider [Collection Hooks](./collections) or [Global Hooks](./globals)
instead.
To add hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload';
const FieldWithHooks: Field = {
name: 'name',
type: 'text',
// highlight-start
hooks: {
beforeValidate: [(args) => {...}],
beforeChange: [(args) => {...}],
beforeDuplicate: [(args) => {...}],
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
}
// highlight-end
}
```
The following arguments are provided to all Field Hooks:
| Option | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. If the field belongs to a Global, this will be `null`. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | In the `afterRead` hook this is the full Document. In the `create` and `update` operations, this is the incoming data passed through the operation. |
| **`field`** | The [Field](../fields/overview) which the Hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. If the field belongs to a Collection, this will be `null`. |
| **`operation`** | The name of the operation that this hook is running within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | In the `update` operation, this is the Document before changes were applied. In the `afterChange` hook, this is the resulting Document. |
| **`overrideAccess`** | A boolean to denote if the current operation is overriding [Access Control](../access-control/overview). |
| **`path`** | The path to the [Field](../fields/overview) in the schema. |
| **`previousDoc`** | In the `afterChange` Hook, this is the Document before changes were applied. |
| **`previousSiblingDoc`** | The sibling data of the Document before changes being applied, only in `beforeChange` and `afterChange` hook. |
| **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`schemaPath`** | The path of the [Field](../fields/overview) in the schema. |
| **`siblingData`** | The data of sibling fields adjacent to the field that the Hook is running against. |
| **`siblingDocWithLocales`** | The sibling data of the Document with all [Locales](../configuration/localization). |
| **`siblingFields`** | The sibling fields of the field which the hook is running against. |
| **`value`** | The value of the [Field](../fields/overview). |
**Tip:** It's a good idea to conditionally scope your logic based on which
operation is executing. For example, if you are writing a `beforeChange` hook,
you may want to perform different logic based on if the current `operation` is
`create` or `update`.
### beforeValidate
Runs during 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 client-side validation. If you render a custom field component in your front-end and provide it with a `validate` function, the order that validations will run in is:
1. `validate` runs on the client
2. if successful, `beforeValidate` runs on the server
3. `validate` runs on the server
```ts
import type { Field } from 'payload'
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 type { Field } from 'payload'
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 type { Field } from 'payload'
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 type { Field } from 'payload'
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.
### beforeDuplicate
The `beforeDuplicate` field hook is called on each locale (when using localization), when duplicating a document. It may be used when documents having the
exact same properties may cause issue. This gives you a way to avoid duplicate names on `unique`, `required` fields or when external systems expect non-repeating values on documents.
This hook gets called before the `beforeValidate` and `beforeChange` hooks are called.
By Default, unique and required text fields Payload will append "- Copy" to the original document value. The default is not added if your field has its own, you must return non-unique values from your beforeDuplicate hook to avoid errors or enable the `disableDuplicate` option on the collection.
Here is an example of a number field with a hook that increments the number to avoid unique constraint errors when duplicating a document:
```ts
import type { Field } from 'payload'
const numberField: Field = {
name: 'number',
type: 'number',
hooks: {
// increment existing value by 1
beforeDuplicate: [
({ value }) => {
return (value ?? 0) + 1
},
],
},
}
```
## TypeScript
Payload exports a type for field hooks which can be accessed and used as follows:
```ts
import type { FieldHook } from 'payload'
// 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
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
}
```
# Context
Source: https://payloadcms.com/docs/hooks/context
The `context` object is used to share data across different Hooks. This persists throughout the entire lifecycle of a request and is available within every Hook. By setting properties to `req.context`, you can effectively share logic across multiple Hooks.
## 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 in the context of a later hook.
For example:
```ts
import type { CollectionConfig } from 'payload'
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
import type { CollectionConfig } from 'payload'
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.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
import type { CollectionConfig } from 'payload'
const MyCollection: CollectionConfig = {
slug: 'slug',
hooks: {
afterChange: [
async ({ context, doc, req }) => {
// return if flag was previously set
if (context.triggerAfterChange === false) {
return
}
await req.payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
context: {
// set a flag to prevent from running again
triggerAfterChange: false,
},
})
},
],
},
fields: [
/* ... */
],
}
```
## TypeScript
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 module` syntax.
This is known as [module augmentation / declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation), a TypeScript feature which allows us to add properties to existing types. Simply put this in any `.ts` or `.d.ts` file:
```ts
declare module 'payload' {
// Augment the RequestContext interface to include your custom properties
export interface RequestContext {
myObject?: string
// ...
}
}
```
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as module augmentation can mess up your types if you do it wrong.
# Local API
Source: https://payloadcms.com/docs/local-api/overview
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.
**Tip:**
The Local API is incredibly powerful when used in React Server Components and other similar server-side contexts. With other headless CMS, you need to request your data from third-party servers via an HTTP layer, which can add significant loading time to your server-rendered pages. With Payload, you don't have to leave your server to gather the data you need. It can be incredibly fast and is definitely a game changer.
Here are some common examples of how you can use the Local API:
- Fetching Payload data within React Server Components
- Seeding data via Node seed scripts that you write and maintain
- Opening custom Next.js route handlers which feature additional functionality but still rely on Payload
- Within [Access Control](../access-control/overview) and [Hooks](../hooks/overview)
## Accessing Payload
You can gain access to the currently running `payload` object via two ways:
#### Accessing from args or `req`
In most places within Payload itself, you can access `payload` directly from the arguments of [Hooks](../hooks/overview), [Access Control](../access-control/overview), [Validation](../fields/overview#validation) functions, and similar. This is the simplest way to access Payload in most cases. Most config functions take the `req` (Request) object, which has Payload bound to it (`req.payload`).
Example:
```ts
const afterChangeHook: CollectionAfterChangeHook = async ({
req: { payload },
}) => {
const posts = await payload.find({
collection: 'posts',
})
}
```
#### Importing it
If you want to import Payload in places where you don't have the option to access it from function arguments or `req`, you can import it and initialize it.
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
const payload = await getPayload({ config })
```
If you're working in Next.js' development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload Config, your usage of Payload will always be in sync with your changes. In production, `getPayload` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
## Local options available
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
| 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](../queries/depth) of nested relationship and upload fields. |
| `locale` | Specify [locale](../configuration/localization) for any returned documents. |
| `select` | Specify [select](../queries/select) to control which fields to include to the result. |
| `populate` | Specify [populate](../queries/select#populate) to control which fields to include to the result from populated documents. |
| `fallbackLocale` | Specify a [fallback locale](../configuration/localization) to use for any returned documents. This can be a single locale or array of locales. |
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents). |
| `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](../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. |
| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
| `disableTransaction` | When set to `true`, a [database transactions](../database/transactions) will not be initialized. |
_There are more options available on an operation by operation basis outlined below._
## Transactions
When your database uses transactions you need to thread req through to all local operations. Postgres uses transactions and MongoDB uses transactions when you are using replica sets. Passing req without transactions is still recommended.
```js
const post = await payload.find({
collection: 'posts',
req, // passing req is recommended
})
```
**Note:**
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.
## Collections
The following Collection operations are available through the Local API:
### Create#collection-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,
// If you want to create a document that is a duplicate of another document
duplicateFromID: 'document-id-to-duplicate',
})
```
### Find#collection-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,
})
```
`pagination`, `page`, and `limit` are three related properties [documented
here](../queries/pagination).
### Find by ID#collection-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#collection-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,
})
```
### FindDistinct#collection-find-distinct
```js
// Result will be an object with:
// {
// values: ['value-1', 'value-2'], // array of distinct values,
// field: 'title', // the field
// totalDocs: 10, // count of the distinct values satisfies query,
// perPage: 10, // count of distinct values per page (based on provided limit)
// }
const result = await payload.findDistinct({
collection: 'posts', // required
locale: 'en',
where: {}, // pass a `where` query here
user: dummyUser,
overrideAccess: false,
field: 'title',
sort: 'title',
})
```
### Update by ID#collection-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,
overrideLock: false, // By default, document locks are ignored. Set to false to enforce locks.
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#collection-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,
overrideLock: false, // By default, document locks are ignored. Set to false to enforce locks.
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#collection-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,
overrideLock: false, // By default, document locks are ignored. Set to false to enforce locks.
showHiddenFields: true,
})
```
### Delete Many#collection-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,
overrideLock: false, // By default, document locks are ignored. Set to false to enforce locks.
showHiddenFields: true,
})
```
## Auth Operations
If a collection has [`Authentication`](../authentication/overview) enabled, additional Local API operations will be
available:
### Auth
```js
// If you're using Next.js, you'll have to import headers from next/headers, like so:
// import { headers as nextHeaders } from 'next/headers'
// you'll also have to await headers inside your function, or component, like so:
// const headers = await nextHeaders()
// If you're using Payload outside of Next.js, you'll have to provide headers accordingly.
// result will be formatted as follows:
// {
// permissions: { ... }, // object containing current user's permissions
// user: { ... }, // currently logged in user's document
// responseHeaders: { ... } // returned headers from the response
// }
const result = await payload.auth({ headers, canSetHeaders: false })
```
### 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, // optional, pass a Request object to be provided to all hooks
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 a Request object to 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, // optional, pass a Request object to be provided to all hooks
})
```
### 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, // optional, pass a Request object to 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#global-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#global-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,
overrideLock: false, // By default, document locks are ignored. Set to false to enforce locks.
showHiddenFields: true,
})
```
## TypeScript
Local API calls will automatically infer your [generated types](../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',
},
})
```
# Using Payload outside Next.js
Source: https://payloadcms.com/docs/local-api/outside-nextjs
Payload can be used completely outside of Next.js which is helpful in cases like running scripts, using Payload in a separate backend service, or using Payload's Local API to fetch your data directly from your database in other frontend frameworks like SvelteKit, Remix, Nuxt, and similar.
**Note:** Payload and all of its official packages are fully ESM. If you want
to use Payload within your own projects, make sure you are writing your
scripts in ESM format or dynamically importing the Payload Config.
## Importing the Payload Config outside of Next.js
Payload provides a convenient way to run standalone scripts, which can be useful for tasks like seeding your database or performing one-off operations.
In standalone scripts, you can simply import the Payload Config and use it right away. If you need an initialized copy of Payload, you can then use the `getPayload` function. This can be useful for tasks like seeding your database or performing other one-off operations.
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
const seed = async () => {
// Get a local copy of Payload by passing your config
const payload = await getPayload({ config })
const user = await payload.create({
collection: 'users',
data: {
email: 'dev@payloadcms.com',
password: 'some-password',
},
})
const page = await payload.create({
collection: 'pages',
data: {
title: 'My Homepage',
// other data to seed here
},
})
}
// Call the function here to run your seed script
await seed()
```
You can then execute the script using `payload run`. Example: if you placed this standalone script in `src/seed.ts`, you would execute it like this:
```sh
payload run src/seed.ts
```
The `payload run` command does two things for you:
1. It loads the environment variables the same way Next.js loads them, eliminating the need for additional dependencies like `dotenv`. The usage of `dotenv` is not recommended, as Next.js loads environment variables differently. By using `payload run`, you ensure consistent environment variable handling across your Payload and Next.js setup.
2. It initializes tsx, allowing direct execution of TypeScript files manually installing tools like tsx or ts-node.
### Troubleshooting
If you encounter import-related errors, you have 2 options:
#### Option 1: enable swc mode by appending `--use-swc` to the `payload` command:
Example:
```sh
payload run src/seed.ts --use-swc
```
Note: Install @swc-node/register in your project first. While swc mode is faster than the default tsx mode, it might break for some imports.
#### Option 2: use an alternative runtime like bun
While we do not guarantee support for alternative runtimes, you are free to use them and disable Payload's own transpilation by appending the `--disable-transpile` flag to the `payload` command:
```sh
bunx --bun payload run src/seed.ts --disable-transpile
```
You will need to have bun installed on your system for this to work.
# Using Local API Operations with Server Functions
Source: https://payloadcms.com/docs/local-api/server-functions
In Next.js, **server functions** (previously called **server actions**) are special functions that run exclusively on the server, enabling secure backend logic execution while being callable from the frontend. These functions bridge the gap between client and server, allowing frontend components to perform backend operations without exposing sensitive logic.
### Why Use Server Functions?
- **Executing Backend Logic from the Frontend**: The Local API is designed for server environments and cannot be directly accessed from client-side code. Server functions enable frontend components to trigger backend operations securely.
- **Security Benefits**: Instead of exposing a full REST or GraphQL API, server functions restrict access to only the necessary operations, reducing potential security risks.
- **Performance Optimizations**: Next.js handles server functions efficiently, offering benefits like caching, optimized database queries, and reduced network overhead compared to traditional API calls.
- **Simplified Development Workflow**: Rather than setting up full API routes with authentication and authorization checks, server functions allow for lightweight, direct execution of necessary operations.
### When to Use Server Functions
Use server functions whenever you need to call Local API operations from the frontend. Since the Local API is only accessible from the backend, server functions act as a secure bridge, eliminating the need to expose additional API endpoints.
## Examples
All Local API operations can be used within server functions, allowing you to interact with Payload's backend securely.
For a full list of available operations, see the [Local API](../local-api/overview) overview.
In the following examples, we'll cover some common use cases, including:
- Creating a document
- Updating a document
- Handling file uploads when creating or updating a document
- Authenticating a user
### Creating a Document
First, let's create our server function. Here are some key points for this process:
- Begin by adding `'use server'` at the top of the file.
- You can still use utilities such as `getPayload()`.
- Once the function structure is in place, call the Local API operation `payload.create()` and pass in the relevant data.
- It's good practice to wrap this in a `try...catch` block for error handling.
- Finally, make sure to return the created document (don't just run the operation).
```ts
'use server'
import { getPayload } from 'payload'
import config from '@payload-config'
export async function createPost(data) {
const payload = await getPayload({ config })
try {
const post = await payload.create({
collection: 'posts',
data,
})
return post
} catch (error) {
throw new Error(`Error creating post: ${error.message}`)
}
}
```
Now, let's look at how to call the `createPost` function we just created from the frontend in a React component when a user clicks a button:
```ts
'use client';
import React, { useState } from 'react';
import { createPost } from '../server/actions'; // import the server function
export const PostForm: React.FC = () => {
const [result, setResult] = useState('');
return (
<>
{result}
>
);
};
```
### Updating a Document
The key points from the previous example also apply here.
To update a document instead of creating one, you would use `payload.update()` with the relevant data and **passing the document ID.**
Here's how the server function would look:
```ts
'use server'
import { getPayload } from 'payload'
import config from '@payload-config'
export async function updatePost(id, data) {
const payload = await getPayload({ config })
try {
const post = await payload.update({
collection: 'posts',
id, // the document id is required
data,
})
return post
} catch (error) {
throw new Error(`Error updating post: ${error.message}`)
}
}
```
Here is how you would call the `updatePost` function from a frontend React component:
```ts
'use client';
import React, { useState } from 'react';
import { updatePost } from '../server/actions'; // import the server function
export const UpdatePostForm: React.FC = () => {
const [result, setResult] = useState('');
return (
<>
{result}
>
);
};
```
### Authenticating a User
In this example, we will check if a user is authenticated using Payload's authentication system. Here's how it works:
- First, we use the headers function from `next/headers` to retrieve the request headers.
- Next, we pass these headers to `payload.auth()` to fetch the user's authentication details.
- If the user is authenticated, their information is returned. If not, handle the unauthenticated case accordingly.
Here's the server function to authenticate a user:
```ts
'use server'
import { headers as getHeaders } from 'next/headers'
import config from '@payload-config'
import { getPayload } from 'payload'
export const authenticateUser = async () => {
const payload = await getPayload({ config })
const headers = await getHeaders()
const { user } = await payload.auth({ headers })
if (user) {
return { hello: user.email }
}
return { hello: 'Not authenticated' }
}
```
Here's a basic example of how to call the authentication server function from the frontend to test it:
```ts
'use client';
import React, { useState } from 'react';
import { authenticateUser } from '../server/actions'; // Import the server function
export const AuthComponent: React.FC = () => {
const [userInfo, setUserInfo] = useState('');
return (
{userInfo}
);
};
```
### Creating a Document with File Upload
This example demonstrates how to write a server function that creates a document with a file upload. Here are the key steps:
- Pass two arguments: **data** for the document content and **upload** for the file
- Merge the upload file into the document data as the media field
- Use `payload.create()` to create a new post document with both the document data and file
```ts
'use server'
import { getPayload } from 'payload'
import config from '@payload-config'
export async function createPostWithUpload(data, upload) {
const payload = await getPayload({ config })
try {
// Prepare the data with the file
const postData = {
...data,
media: upload,
}
const post = await payload.create({
collection: 'posts',
data: postData,
})
return post
} catch (error) {
throw new Error(`Error creating post: ${error.message}`)
}
}
```
Here is how you would use the server function we just created in a frontend component to allow users to submit a post along with a file upload:
- The user enters the post title and selects a file to upload.
- When the form is submitted, the `handleSubmit` function checks if a file has been chosen.
- If a file is selected, it passes both the title and the file to the `createPostWithFile` server function.
- And you are done!
```ts
'use client';
import React, { useState } from 'react';
import { createPostWithUpload } from '../server/actions';
export const PostForm: React.FC = () => {
const [title, setTitle] = useState('');
const [file, setFile] = useState(null);
const [result, setResult] = useState('');
const handleFileChange = (e: React.ChangeEvent) => {
if (e.target.files) {
setFile(e.target.files[0]);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
setResult('Please upload a file.');
return;
}
try {
// Call the server function to create the post with the file
const newPost = await createPostWithUpload({ title }, file);
setResult('Post created with file: ' + newPost.title);
} catch (error) {
setResult('Error: ' + error.message);
}
};
return (
);
};
```
## Reusable Payload Server Functions
Managing authentication with the Local API can be tricky as you have to handle cookies and tokens yourself, and there aren't built-in logout or refresh functions since these only modify cookies. To make this easier, we provide `login`, `logout`, and `refresh` as ready-to-use server functions. They take care of the underlying complexity so you don't have to.
### Login
Logs in a user by verifying credentials and setting the authentication cookie. This function allows login via username or email, depending on the collection auth configuration.
#### Importing the `login` function
```ts
import { login } from '@payloadcms/next/auth'
```
The login function needs your Payload config, which cannot be imported in a client component. To work around this, create a simple server function like the one below, and call it from your client.
```ts
'use server'
import { login } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function loginAction({
email,
password,
}: {
email: string
password: string
}) {
try {
const result = await login({
collection: 'users',
config,
email,
password,
})
return result
} catch (error) {
throw new Error(
`Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Login from the React Client Component
```tsx
'use client'
import { useState } from 'react'
import { loginAction } from '../loginAction'
export default function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
return (
)
}
```
### Logout
Logs out the current user by clearing the authentication cookie and current sessions.
#### Importing the `logout` function
```ts
import { logout } from '@payloadcms/next/auth'
```
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below. To ensure all sessions are cleared, set `allSessions: true` in the options, if you wish to logout but keep current sessions active, you can set this to `false` or leave it `undefined`.
```ts
'use server'
import { logout } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function logoutAction() {
try {
return await logout({ allSessions: true, config })
} catch (error) {
throw new Error(
`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Logout from the React Client Component
```tsx
'use client'
import { logoutAction } from '../logoutAction'
export default function LogoutButton() {
return
}
```
### Refresh
Refreshes the authentication token and current session for the logged-in user.
#### Importing the `refresh` function
```ts
import { refresh } from '@payloadcms/next/auth'
```
As with login and logout, you need to pass your Payload config to this function. Create a helper server function like the one below. Passing the config directly to the client is not possible and will throw errors.
```ts
'use server'
import { refresh } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function refreshAction() {
try {
return await refresh({
config,
})
} catch (error) {
throw new Error(
`Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Using Refresh from the React Client Component
```tsx
'use client'
import { refreshAction } from '../actions/refreshAction'
export default function RefreshTokenButton() {
return
}
```
## Error Handling in Server Functions
When using server functions, proper error handling is essential to prevent unhandled exceptions and provide meaningful feedback to the frontend.
### Best Practices#error-handling-best-practices
- Wrap Local API calls in **try/catch blocks** to catch potential errors.
- **Log errors** on the server for debugging purposes.
- Return structured **error responses** instead of exposing raw errors to the frontend.
Example of good error handling:
```ts
export async function createPost(data) {
try {
const payload = await getPayload({ config })
return await payload.create({ collection: 'posts', data })
} catch (error) {
console.error('Error creating post:', error)
return { error: 'Failed to create post' }
}
}
```
## Security Considerations
Using server functions helps prevent direct exposure of Local API operations to the frontend, but additional security best practices should be followed:
### Best Practices#security-best-practices
- **Restrict access**: Ensure that sensitive actions (like user management) are only callable by authorized users.
- **Avoid passing sensitive data**: Do not return sensitive information such as user data, passwords, etc.
- **Use authentication & authorization**: Check user roles before performing actions.
Example of restricting access based on user role:
```ts
export async function deletePost(postId, user) {
if (!user || user.role !== 'admin') {
throw new Error('Unauthorized')
}
const payload = await getPayload({ config })
return await payload.delete({ collection: 'posts', id: postId })
}
```
# Respecting Access Control with Local API Operations
Source: https://payloadcms.com/docs/local-api/access-control
In Payload, local API operations **override access control by default**. This means that operations will run without checking if the current user has permission to perform the action. This is useful in certain scenarios where access control is not necessary, but it is important to be aware of when to enforce it for security reasons.
### Default Behavior: Access Control Skipped
By default, **local API operations skip access control**. This allows operations to execute without the system checking if the current user has appropriate permissions. This might be helpful in admin or server-side scripts where the user context is not required to perform the operation.
#### For example:
```ts
// Access control is this operation would be skipped by default
const test = await payload.create({
collection: 'users',
data: {
email: 'test@test.com',
password: 'test',
},
})
```
### Respecting Access Control
If you want to respect access control and ensure that the operation is performed only if the user has appropriate permissions, you need to explicitly pass the `user` object and set the `overrideAccess` option to `false`.
- `overrideAccess: false`: This ensures that access control is **not skipped** and the operation respects the current user's permissions.
- `user`: Pass the authenticated user context to the operation. This ensures the system checks whether the user has the right permissions to perform the action.
```ts
const authedCreate = await payload.create({
collection: 'users',
overrideAccess: false, // This ensures access control will be applied
user, // Pass the authenticated user to check permissions
data: {
email: 'test@test.com',
password: 'test',
},
})
```
This example will only allow the document to be created if the `user` we passed has the appropriate access control permissions.
# REST API
Source: https://payloadcms.com/docs/rest-api/overview
A fully functional REST API is automatically generated from your Collection
and Global configs.
The REST API is a fully functional HTTP client that allows you to interact with your Documents in a RESTful manner. It supports all CRUD operations and is equipped with automatic pagination, depth, and sorting.
All Payload API routes are mounted and prefixed to your config's `routes.api` URL segment (default: `/api`).
To enhance DX, you can use [Payload SDK](#payload-rest-api-sdk) to query your REST API.
**REST query parameters:**
- [depth](../queries/depth) - automatically populates relationships and uploads
- [locale](../configuration/localization#retrieving-localized-docs) - retrieves document(s) in a specific locale
- [fallback-locale](../configuration/localization#retrieving-localized-docs) - specifies a fallback locale if no locale value exists
- [select](../queries/select) - specifies which fields to include to the result
- [populate](../queries/select#populate) - specifies which fields to include to the result from populated documents
- [limit](../queries/pagination#pagination-controls) - limits the number of documents returned
- [page](../queries/pagination#pagination-controls) - specifies which page to get documents from when used with a limit
- [sort](../queries/sort#rest-api) - specifies the field(s) to use to sort the returned documents by
- [where](../queries/overview) - specifies advanced filters to use to query documents
- [joins](../fields/join#rest-api) - specifies the custom request for each join field by name of the field
## 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:**
## Auth Operations
Auth enabled collections are also given the following endpoints:
## Globals
Globals cannot be created or deleted, so there are only two REST endpoints opened:
## Preferences
In addition to the dynamically generated endpoints above Payload also has REST endpoints to manage the admin user [preferences](../admin/preferences) for data specific to the authenticated user.
## 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.
Custom endpoints are not authenticated by default. You are responsible for
securing your own endpoints.
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 that accepts **req** - `PayloadRequest` object which contains [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) properties, currently authenticated `user` and the Local API instance `payload`. |
| **`root`** | When `true`, defines the endpoint on the root Next.js 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 type { CollectionConfig } from 'payload'
// 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) => {
const tracking = await getTrackingInfo(req.routeParams.id)
if (!tracking) {
return Response.json({ error: 'not found' }, { status: 404 })
}
return Response.json({
message: `Hello ${req.routeParams.name as string} @ ${req.routeParams.group as string}`,
})
},
},
{
path: '/:id/tracking',
method: 'post',
handler: async (req) => {
// `data` is not automatically appended to the request
// if you would like to read the body of the request
// you can use `data = await req.json()`
const data = await req.json()
await req.payload.update({
collection: 'tracking',
data: {
// data to update the document with
},
})
return Response.json({
message: 'successfully updated tracking info',
})
},
},
{
path: '/:id/forbidden',
method: 'post',
handler: async (req) => {
// this is an example of an authenticated endpoint
if (!req.user) {
return Response.json({ error: 'forbidden' }, { status: 403 })
}
// do something
return Response.json({
message: 'successfully updated tracking info',
})
},
},
],
// highlight-end
}
```
**Note:** **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](../access-control/overview) and
[Hooks](../hooks/overview).
#### Helpful tips
`req.data`
Data is not automatically appended to the request. You can read the body data by calling `await req.json()`.
Or you could use our helper function that mutates the request and appends data and file if found.
```ts
import { addDataAndFileToRequest } from 'payload'
// custom endpoint example
{
path: '/:id/tracking',
method: 'post',
handler: async (req) => {
await addDataAndFileToRequest(req)
await req.payload.update({
collection: 'tracking',
data: {
// data to update the document with
}
})
return Response.json({
message: 'successfully updated tracking info'
})
}
}
```
`req.locale` & `req.fallbackLocale`
The locale and the fallback locale are not automatically appended to custom endpoint requests. If you would like to add them you can use this helper function.
```ts
import { addLocalesToRequestFromData } from 'payload'
// custom endpoint example
{
path: '/:id/tracking',
method: 'post',
handler: async (req) => {
await addLocalesToRequestFromData(req)
// you now can access req.locale & req.fallbackLocale
return Response.json({ message: 'success' })
}
}
```
`headersWithCors`
By default, custom endpoints don't handle CORS headers in responses. The `headersWithCors` function checks the Payload config and sets the appropriate CORS headers in the response accordingly.
```ts
import { headersWithCors } from 'payload'
// custom endpoint example
{
path: '/:id/tracking',
method: 'post',
handler: async (req) => {
return Response.json(
{ message: 'success' },
{
headers: headersWithCors({
headers: new Headers(),
req,
})
},
)
}
}
```
## Method Override for GET Requests
Payload supports a method override feature that allows you to send GET requests using the HTTP POST method. This can be particularly useful in scenarios when the query string in a regular GET request is too long.
### How to Use
To use this feature, include the `X-Payload-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
### Example
Here is an example of how to use the method override to perform a GET request:
#### Using Method Override (POST)
```ts
const res = await fetch(`${api}/${collectionSlug}`, {
method: 'POST',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/x-www-form-urlencoded',
'X-Payload-HTTP-Method-Override': 'GET',
},
body: qs.stringify({
depth: 1,
locale: 'en',
}),
})
```
#### Equivalent Regular GET Request
```ts
const res = await fetch(`${api}/${collectionSlug}?depth=1&locale=en`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
},
})
```
### Passing as JSON
When using `X-Payload-HTTP-Method-Override`, it expects the body to be a query string. If you want to pass JSON instead, you can set the `Content-Type` to `application/json` and include the JSON body in the request.
#### Example
```ts
const res = await fetch(`${api}/${collectionSlug}/${id}`, {
// Only the findByID endpoint supports HTTP method overrides with JSON data
method: 'POST',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
'X-Payload-HTTP-Method-Override': 'GET',
},
body: JSON.stringify({
depth: 1,
locale: 'en',
}),
})
```
This can be more efficient for large JSON payloads, as you avoid converting data to and from query strings. However, only certain endpoints support this. Supported endpoints will read the parsed body under a `data` property, instead of reading from query parameters as with standard GET requests.
## Payload REST API SDK
The best, fully type-safe way to query Payload REST API is to use the SDK package, which can be installed with:
```bash
pnpm add @payloadcms/sdk
```
Its usage is very similar to [the Local API](../local-api/overview).
**Note:** The SDK package is currently in beta and may be subject to change in
minor versions updates prior to being stable.
Example:
```ts
import { PayloadSDK } from '@payloadcms/sdk'
import type { Config } from './payload-types'
// Pass your config from generated types as generic
const sdk = new PayloadSDK({
baseURL: 'https://example.com/api',
})
// Find operation
const posts = await sdk.find({
collection: 'posts',
draft: true,
limit: 10,
locale: 'en',
page: 1,
where: { _status: { equals: 'published' } },
})
// Find by ID operation
const posts = await sdk.findByID({
id,
collection: 'posts',
draft: true,
locale: 'en',
})
// Auth login operation
const result = await sdk.login({
collection: 'users',
data: {
email: 'dev@payloadcms.com',
password: '12345',
},
})
// Create operation
const result = await sdk.create({
collection: 'posts',
data: { text: 'text' },
})
// Create operation with a file
// `file` can be either a Blob | File object or a string URL
const result = await sdk.create({ collection: 'media', file, data: {} })
// Count operation
const result = await sdk.count({
collection: 'posts',
where: { id: { equals: post.id } },
})
// Update (by ID) operation
const result = await sdk.update({
collection: 'posts',
id: post.id,
data: {
text: 'updated-text',
},
})
// Update (bulk) operation
const result = await sdk.update({
collection: 'posts',
where: {
id: {
equals: post.id,
},
},
data: { text: 'updated-text-bulk' },
})
// Delete (by ID) operation
const result = await sdk.delete({ id: post.id, collection: 'posts' })
// Delete (bulk) operation
const result = await sdk.delete({
where: { id: { equals: post.id } },
collection: 'posts',
})
// Find Global operation
const result = await sdk.findGlobal({ slug: 'global' })
// Update Global operation
const result = await sdk.updateGlobal({
slug: 'global',
data: { text: 'some-updated-global' },
})
// Auth Login operation
const result = await sdk.login({
collection: 'users',
data: { email: 'dev@payloadcms.com', password: '123456' },
})
// Auth Me operation
const result = await sdk.me(
{ collection: 'users' },
{
headers: {
Authorization: `JWT ${user.token}`,
},
},
)
// Auth Refresh Token operation
const result = await sdk.refreshToken(
{ collection: 'users' },
{ headers: { Authorization: `JWT ${user.token}` } },
)
// Auth Forgot Password operation
const result = await sdk.forgotPassword({
collection: 'users',
data: { email: user.email },
})
// Auth Reset Password operation
const result = await sdk.resetPassword({
collection: 'users',
data: { password: '1234567', token: resetPasswordToken },
})
// Find Versions operation
const result = await sdk.findVersions({
collection: 'posts',
where: { parent: { equals: post.id } },
})
// Find Version by ID operation
const result = await sdk.findVersionByID({
collection: 'posts',
id: version.id,
})
// Restore Version operation
const result = await sdk.restoreVersion({
collection: 'posts',
id,
})
// Find Global Versions operation
const result = await sdk.findGlobalVersions({
slug: 'global',
})
// Find Global Version by ID operation
const result = await sdk.findGlobalVersionByID({
id: version.id,
slug: 'global',
})
// Restore Global Version operation
const result = await sdk.restoreGlobalVersion({
slug: 'global',
id,
})
```
Every operation has optional 3rd parameter which is used to add additional data to the RequestInit object (like headers):
```ts
await sdk.me(
{
collection: 'users',
},
{
// RequestInit object
headers: {
Authorization: `JWT ${token}`,
},
},
)
```
To query custom endpoints, you can use the `request` method, which is used internally for all other methods:
```ts
await sdk.request({
method: 'POST',
path: '/send-data',
json: {
id: 1,
},
})
```
Custom `fetch` implementation and `baseInit` for shared `RequestInit` properties:
```ts
const sdk = new PayloadSDK({
baseInit: { credentials: 'include' },
baseURL: 'https://example.com/api',
fetch: async (url, init) => {
console.log('before req')
const response = await fetch(url, init)
console.log('after req')
return response
},
})
```
Example of a custom `fetch` implementation for testing the REST API without needing to spin up a next development server:
```ts
import type { GeneratedTypes, SanitizedConfig } from 'payload'
import config from '@payload-config'
import {
REST_DELETE,
REST_GET,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'
import { PayloadSDK } from '@payloadcms/sdk'
export type TypedPayloadSDK = PayloadSDK
const api = {
GET: REST_GET(config),
POST: REST_POST(config),
PATCH: REST_PATCH(config),
DELETE: REST_DELETE(config),
PUT: REST_PUT(config),
}
const awaitedConfig = await config
export const sdk = new PayloadSDK({
baseURL: ``,
fetch: (path: string, init: RequestInit) => {
const [slugs, search] = path.slice(1).split('?')
const url = `${awaitedConfig.serverURL || 'http://localhost:3000'}${awaitedConfig.routes.api}/${slugs}${search ? `?${search}` : ''}`
if (init.body instanceof FormData) {
const file = init.body.get('file') as Blob
if (file && init.headers instanceof Headers) {
init.headers.set('Content-Length', file.size.toString())
}
}
const request = new Request(url, init)
const params = {
params: Promise.resolve({
slug: slugs.split('/'),
}),
}
return api[init.method.toUpperCase()](request, params)
},
})
```
# GraphQL Overview
Source: https://payloadcms.com/docs/graphql/overview
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](../graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](../graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](../graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground in production environments, defaults to true. [More](../graphql/overview#graphql-playground) |
| `disableIntrospectionInProduction` | A boolean that if false will enable the GraphQL introspection in production environments, defaults to true. |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
## 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 type { CollectionConfig } from 'payload'
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 type { GlobalConfig } from 'payload';
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](../admin/preferences) for the [Admin Panel](../admin/overview) 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.
**Tip:**
To see more regarding how the above queries and mutations are used, visit your GraphQL playground
(by default at
[`${SERVER_URL}/api/graphql-playground`](http://localhost:3000/api/graphql-playground))
while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to
see a ton of detail about how GraphQL operates within Payload.
## Custom Validation Rules
You can add custom validation rules to your GraphQL API by defining a `validationRules` function in your Payload Config. This function should return an array of [Validation Rules](https://graphql.org/graphql-js/validation/#validation-rules) that will be applied to all incoming queries and mutations.
```ts
import { GraphQL } from '@payloadcms/graphql/types'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
graphQL: {
validationRules: (args) => [NoProductionIntrospection],
},
// ...
})
const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
Field(node) {
if (process.env.NODE_ENV === 'production') {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQL.GraphQLError(
'GraphQL introspection is not allowed, but the query contained __schema or __type',
{ nodes: [node] },
),
)
}
}
},
})
```
## 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](../production/preventing-abuse#limiting-graphql-complexity).
## Field complexity
You can define custom complexity for `relationship`, `upload` and `join` type fields. This is useful if you want to assign a higher complexity to a field that is more expensive to resolve. This can help prevent users from running queries that are too complex.
```ts
const fieldWithComplexity = {
name: 'authors',
type: 'relationship',
relationship: 'authors',
graphQL: {
complexity: 100, // highlight-line
},
}
```
# Adding your own Queries and Mutations
Source: https://payloadcms.com/docs/graphql/extending
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'
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/).
**`GraphQLJSON`** & **`GraphQLJSONObject`**
```ts
import { GraphQLJSON, GraphQLJSONObject } from '@payloadcms/graphql/types'
```
**`GraphQL`**
You can directly import the GraphQL package used by Payload, most useful for typing.
```ts
import { GraphQL } from '@payloadcms/graphql/types'
```
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
import { buildPaginatedListType } from '@payloadcms/graphql/types'
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
updateMutationInputType: GraphQLNonNull
}
```
## 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/
```
# GraphQL Schema
Source: https://payloadcms.com/docs/graphql/graphql-schema
In Payload the schema is controlled by your collections and globals. All you need to do is run the generate command and the entire schema will be created for you.
## Schema generation script
Install `@payloadcms/graphql` as a dev dependency:
```bash
pnpm add @payloadcms/graphql -D
```
Run the following command to generate the schema:
```bash
pnpm payload-graphql generate:schema
```
## 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', // highlight-line
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'text',
},
],
}
```
will generate:
```ts
// A top level reusable type will be generated
type SharedMeta {
title: String
description: String
}
// And will be referenced inside the generated 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
**Important**
Payload needs to be able to find your config to generate your GraphQL schema.
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, create an npm script to make generating types easier:
```json
// package.json
{
"scripts": {
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload-graphql generate:schema"
}
}
```
Now you can run `pnpm generate:graphQLSchema` to easily generate your schema.
# Querying your Documents
Source: https://payloadcms.com/docs/queries/overview
In Payload, "querying" means filtering or searching through Documents within a [Collection](../configuration/collections). The querying language in Payload is designed to be simple and powerful, allowing you to filter Documents with extreme precision through an intuitive and standardized structure.
Payload provides three common APIs for querying your data:
- [Local API](../local-api/overview) - Extremely fast, direct-to-database access
- [REST API](../rest-api/overview) - Standard HTTP endpoints for querying and mutating data
- [GraphQL](../graphql/overview) - A full GraphQL API with a GraphQL Playground
Each of these APIs share the same underlying querying language, and fully support all of the same features. This means that you can learn Payload's querying language once, and use it across any of the APIs that you might use.
To query your Documents, you can send any number of [Operators](#operators) through your request:
```ts
import type { Where } from 'payload'
const query: Where = {
color: {
equals: 'blue',
},
}
```
_The exact query syntax will depend on the API you are using, but the concepts are the same across all APIs. [More details](#writing-queries)._
**Tip:** You can also use queries within [Access
Control](../access-control/overview) functions.
## Operators
The following operators are available for use in queries:
| 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. Note: currently this operator is supported only with the MongoDB adapter. |
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
| `near` | For distance related to a [Point Field](../fields/point) comma separated as `, , , `. |
| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) |
| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) |
**Tip:** If you know your users will be querying on certain fields a lot, add
`index: true` to the Field Config. This will speed up searches using that
field immensely. [More details](../database/indexes).
### And / Or Logic
In addition to defining simple queries, you can join multiple queries together using AND / OR logic. These can be nested as deeply as you need to create complex queries.
To join queries, use the `and` or `or` keys in your query object:
```ts
import type { Where } from 'payload'
const query: Where = {
or: [
// highlight-line
{
color: {
equals: 'mint',
},
},
{
and: [
// highlight-line
{
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
import type { Where } from 'payload'
const query: Where = {
'artists.featured': {
// nested property name to filter on
exists: true, // operator to use and boolean value that needs to be true
},
}
```
## Writing Queries
Writing queries in Payload is simple and consistent across all APIs, with only minor differences in syntax between them.
### Local API
The [Local API](../local-api/overview) supports the `find` operation that accepts a raw query object:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
where: {
color: {
equals: 'mint',
},
},
})
return posts
}
```
### GraphQL API
All `find` queries in the [GraphQL API](../graphql/overview) support the `where` argument that accepts a raw query object:
```ts
query {
Posts(where: { color: { equals: mint } }) {
docs {
color
}
totalDocs
}
}
```
### REST API
With the [REST API](../rest-api/overview), you can use the full power of Payload queries, but they are written as query strings instead:
**`https://localhost:3000/api/posts?where[color][equals]=mint`**
To understand the syntax, you need to understand that complex URL search strings are parsed into a JSON object. This one isn't too bad, but more complex queries get unavoidably more difficult to write.
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs-esm`](https://www.npmjs.com/package/qs-esm) package to parse your JSON / object-formatted queries into query strings:
```ts
import { stringify } from 'qs-esm'
import type { Where } from 'payload'
const query: Where = {
color: {
equals: 'mint',
},
// This query could be much more complex
// and qs-esm would handle it beautifully
}
const getPosts = async () => {
const stringifiedQuery = stringify(
{
where: query, // ensure that `qs-esm` adds the `where` property, too!
},
{ addQueryPrefix: true },
)
const response = await fetch(
`http://localhost:3000/api/posts${stringifiedQuery}`,
)
// Continue to handle the response below...
}
```
## Performance
There are several ways to optimize your queries. Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these strategies together as possible to ensure your queries are as performant as they can be.
For more performance tips, see the [Performance
documentation](../performance/overview).
### Indexes
Build [Indexes](../database/indexes) for fields that are often queried or sorted by.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
This is done by adding `index: true` to the Field Config for that field:
```ts
// In your collection configuration
{
name: 'posts',
fields: [
{
name: 'title',
type: 'text',
// highlight-start
index: true, // Add an index to the title field
// highlight-end
},
// Other fields...
],
}
```
To learn more, see the [Indexes documentation](../database/indexes).
### Depth
Set the [Depth](./depth) to only the level that you need to avoid populating unnecessary related documents.
Relationships will only populate down to the specified depth, and any relationships beyond that depth will only return the ID of the related document.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
depth: 0, // Only return the IDs of related documents
// highlight-end
})
```
To learn more, see the [Depth documentation](./depth).
### Limit
Set the [Limit](./pagination#limit) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
limit: 1, // Only expect one document to be returned
// highlight-end
})
```
**Tip:** Use in combination with `pagination: false` for best performance when
querying by unique fields.
To learn more, see the [Limit documentation](./pagination#limit).
### Select
Use the [Select API](./select) to only process and return the fields you need.
This will reduce the amount of data returned from the request, and also skip processing of any fields that are not selected, such as running their field hooks.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
select: [{
title: true,
}],
// highlight-end
```
This is a basic example, but there are many ways to use the Select API, including selecting specific fields, excluding fields, etc.
To learn more, see the [Select documentation](./select).
### Pagination
[Disable Pagination](./pagination#disabling-pagination) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
pagination: false, // Return all matched documents without pagination
// highlight-end
})
```
**Tip:** Use in combination with `limit: 1` for best performance when querying
by unique fields.
To learn more, see the [Pagination documentation](./pagination).
# Sort
Source: https://payloadcms.com/docs/queries/sort
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order.
If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) unless it's [linked with a relationship field](../fields/relationship#linking-virtual-fields-with-relationships). It must be stored in the database to be searchable.
**Tip:** For performance reasons, it is recommended to enable `index: true`
for the fields that will be sorted upon. [More details](../database/indexes).
## Local API
To sort Documents in the [Local API](../local-api/overview), you can use the `sort` option in your query:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
sort: '-createdAt', // highlight-line
})
return posts
}
```
To sort by multiple fields, you can use the `sort` option with fields in an array:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
sort: ['priority', '-createdAt'], // highlight-line
})
return posts
}
```
## REST API
To sort in the [REST API](../rest-api/overview), you can use the `sort` parameter in your query:
```ts
fetch('https://localhost:3000/api/posts?sort=-createdAt') // highlight-line
.then((response) => response.json())
.then((data) => console.log(data))
```
To sort by multiple fields, you can use the `sort` parameter with fields separated by comma:
```ts
fetch('https://localhost:3000/api/posts?sort=priority,-createdAt') // highlight-line
.then((response) => response.json())
.then((data) => console.log(data))
```
## GraphQL API
To sort in the [GraphQL API](../graphql/overview), you can use the `sort` parameter in your query:
```ts
query {
Posts(sort: "-createdAt") {
docs {
color
}
}
}
```
# Depth
Source: https://payloadcms.com/docs/queries/depth
Documents in Payload can have relationships to other Documents. This is true for both [Collections](../configuration/collections) as well as [Globals](../configuration/globals). When you query a Document, you can specify the depth at which to populate any of its related Documents either as full objects, or only their IDs.
Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates. Depth can impact the performance of your queries by affecting the load on the database and the size of the response.
For example, when you specify a `depth` of `0`, the API response might look like this:
```json
{
"id": "5ae8f9bde69e394e717c8832",
"title": "This is a great post",
"author": "5f7dd05cd50d4005f8bcab17"
}
```
But with a `depth` of `1`, the response might look like this:
```json
{
"id": "5ae8f9bde69e394e717c8832",
"title": "This is a great post",
"author": {
"id": "5f7dd05cd50d4005f8bcab17",
"name": "John Doe"
}
}
```
**Important:** Depth has no effect in the [GraphQL API](../graphql/overview),
because there, depth is based on the shape of your queries.
## Local API
To specify depth in the [Local API](../local-api/overview), you can use the `depth` option in your query:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// highlight-start
depth: 2,
// highlight-end
})
return posts
}
```
**Reminder:** This is the same for [Globals](../configuration/globals) using
the `findGlobal` operation.
## REST API
To specify depth in the [REST API](../rest-api/overview), you can use the `depth` parameter in your query:
```ts
// highlight-start
fetch('https://localhost:3000/api/posts?depth=2')
// highlight-end
.then((res) => res.json())
.then((data) => console.log(data))
```
**Reminder:** This is the same for [Globals](../configuration/globals) using
the `/api/globals` endpoint.
## Default Depth
If no depth is specified in the request, Payload will use its default depth for all requests. By default, this is set to `2`.
To change the default depth on the application level, you can use the `defaultDepth` option in your root Payload config:
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
// ...
// highlight-start
defaultDepth: 1,
// highlight-end
// ...
})
```
## Max Depth
Fields like the [Relationship Field](../fields/relationship) or the [Upload Field](../fields/upload) can also set a maximum depth. If exceeded, this will limit the population depth regardless of what the depth might be on the request.
To set a max depth for a field, use the `maxDepth` property in your field configuration:
```js
{
slug: 'posts',
fields: [
{
name: 'author',
type: 'relationship',
relationTo: 'users',
// highlight-start
maxDepth: 2,
// highlight-end
}
]
}
```
# Select
Source: https://payloadcms.com/docs/queries/select
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response.
With the Select API, you can define exactly which fields you'd like to retrieve. This can impact the performance of your queries by affecting the load on the database and the size of the response.
## Local API
To specify `select` in the [Local API](../local-api/overview), you can use the `select` option in your query:
```ts
import type { Payload } from 'payload'
// Include mode
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// highlight-start
select: {
text: true,
// select a specific field from group
group: {
number: true,
},
// select all fields from array
array: true,
},
// highlight-end
})
return posts
}
// Exclude mode
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// Select everything except for array and group.number
// highlight-start
select: {
array: false,
group: {
number: false,
},
},
// highlight-end
})
return posts
}
```
**Important:** To perform querying with `select` efficiently, Payload
implements your `select` query on the database level. Because of that, your
`beforeRead` and `afterRead` hooks may not receive the full `doc`. To ensure
that some fields are always selected for your hooks / access control,
regardless of the `select` query you can use `forceSelect` collection config
property.
## REST API
To specify select in the [REST API](../rest-api/overview), you can use the `select` parameter in your query:
```ts
fetch(
// highlight-start
'https://localhost:3000/api/posts?select[color]=true&select[group][number]=true',
// highlight-end
)
.then((res) => res.json())
.then((data) => console.log(data))
```
To understand the syntax, you need to understand that complex URL search strings are parsed into a JSON object. This one isn't too bad, but more complex queries get unavoidably more difficult to write.
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs-esm`](https://www.npmjs.com/package/qs-esm) package to parse your JSON / object-formatted queries into query strings:
```ts
import { stringify } from 'qs-esm'
import type { Where } from 'payload'
const select: Where = {
text: true,
group: {
number: true,
},
// This query could be much more complex
// and QS would handle it beautifully
}
const getPosts = async () => {
const stringifiedQuery = stringify(
{
select, // ensure that `qs` adds the `select` property, too!
},
{ addQueryPrefix: true },
)
const response = await fetch(
`http://localhost:3000/api/posts${stringifiedQuery}`,
)
// Continue to handle the response below...
}
```
**Reminder:** This is the same for [Globals](../configuration/globals) using
the `/api/globals` endpoint.
## defaultPopulate collection config property
The `defaultPopulate` property allows you specify which fields to select when populating the collection from another document.
This is especially useful for links where only the `slug` is needed instead of the entire document.
With this feature, you can dramatically reduce the amount of JSON that is populated from [Relationship](../fields/relationship) or [Upload](../fields/upload) fields.
For example, in your content model, you might have a `Link` field which links out to a different page. When you go to retrieve these links, you really only need the `slug` of the page.
Loading all of the page content, its related links, and everything else is going to be overkill and will bog down your Payload APIs. Instead, you can define the `defaultPopulate` property on your `Pages` collection, so that when Payload "populates" a related Page, it only selects the `slug` field and therefore returns significantly less JSON:
```ts
import type { CollectionConfig } from 'payload'
// The TSlug generic can be passed to have type safety for `defaultPopulate`.
// If avoided, the `defaultPopulate` type resolves to `SelectType`.
export const Pages: CollectionConfig<'pages'> = {
slug: 'pages',
// Specify `select`.
defaultPopulate: {
slug: true,
},
fields: [
{
name: 'slug',
type: 'text',
required: true,
},
],
}
```
**Important:** When using `defaultPopulate` on a collection with
[Uploads](../fields/upload) enabled and you want to select the `url` field,
it is important to specify `filename: true` as well, otherwise Payload will
not be able to construct the correct file URL, instead returning `url: null`.
## Populate
Setting `defaultPopulate` will enforce that each time Payload performs a "population" of a related document, only the fields specified will be queried and returned. However, you can override `defaultPopulate` with the `populate` property in the Local and REST API:
**Local API:**
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
populate: {
// Select only `text` from populated docs in the "pages" collection
// Now, no matter what the `defaultPopulate` is set to on the "pages" collection,
// it will be overridden, and the `text` field will be returned instead.
pages: {
text: true,
}, // highlight-line
},
})
return posts
}
```
**REST API:**
```ts
fetch('https://localhost:3000/api/posts?populate[pages][text]=true') // highlight-line
.then((res) => res.json())
.then((data) => console.log(data))
```
# Pagination
Source: https://payloadcms.com/docs/queries/pagination
With Pagination you can limit the number of documents returned per page, and get a specific page of results. This is useful for creating paginated lists of documents within your application.
All paginated responses include documents nested within a `docs` array, and return top-level meta data related to pagination such as `totalDocs`, `limit`, `totalPages`, `page`, and more.
**Note:** Collection `find` queries are paginated automatically.
## Options
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
| Control | Default | Description |
| ------------ | ------- | ------------------------------------------------------------------------- |
| `limit` | `10` | Limits the number of documents returned per page. [More details](#limit). |
| `pagination` | `true` | Set to `false` to disable pagination and return all documents. |
| `page` | `1` | Get a specific page number. |
## Local API
To specify pagination controls in the [Local API](../local-api/overview), you can use the `limit`, `page`, and `pagination` options in your query:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// highlight-start
limit: 10,
page: 2,
// highlight-end
})
return posts
}
```
## REST API
With the [REST API](../rest-api/overview), you can use the pagination controls below as query strings:
```ts
// highlight-start
fetch('https://localhost:3000/api/posts?limit=10&page=2')
// highlight-end
.then((res) => res.json())
.then((data) => console.log(data))
```
## Response
All paginated responses include documents nested within a `docs` array, and return top-level meta data related to pagination.
The `find` operation includes the following properties in its response:
| 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
}
```
## Limit
You can specify a `limit` to restrict the number of documents returned per page.
**Reminder:** By default, any query with `limit: 0` will automatically
[disable pagination](#disabling-pagination).
#### Performance benefits
If you are querying for a specific document and can reliably expect only one document to match, you can set a limit of `1` (or another low number) to reduce the number of database lookups and improve performance.
For example, when querying a document by a unique field such as `slug`, you can set the limit to `1` since you know there will only be one document with that slug.
To do this, set the `limit` option in your query:
```ts
await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'post-1',
},
},
// highlight-start
limit: 1,
// highlight-end
})
```
## Disabling pagination
Disabling pagination can improve performance by reducing the overhead of pagination calculations and improve query speed.
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.
To do this, set `pagination: false` in your query:
```ts
import type { Payload } from 'payload'
const getPost = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
where: {
title: { equals: 'My Post' },
},
// highlight-start
pagination: false,
// highlight-end
})
return posts
}
```
# The Admin Panel
Source: https://payloadcms.com/docs/admin/overview
Payload dynamically generates a beautiful, [fully type-safe](../typescript/overview) Admin Panel to manage your users and data. It is highly performant, even with 100+ fields, and is translated in over 30 languages. Within the Admin Panel you can manage content, [render your site](../live-preview/overview), [preview drafts](./preview), [diff versions](../versions/overview), and so much more.
The Admin Panel is designed to [white-label your brand](https://payloadcms.com/blog/white-label-admin-ui). You can endlessly customize and extend the Admin UI by swapping in your own [Custom Components](../custom-components/overview)—everything from simple field labels to entire views can be modified or replaced to perfectly tailor the interface for your editors.
The Admin Panel is written in [TypeScript](https://www.typescriptlang.org) and built with [React](https://react.dev) using the [Next.js App Router](https://nextjs.org/docs/app). It supports [React Server Components](https://react.dev/reference/rsc/server-components), enabling the use of the [Local API](../local-api/overview) on the front-end. You can install Payload into any [existing Next.js app in just one line](../getting-started/installation) and [deploy it anywhere](../production/deployment).
The Payload Admin Panel is designed to be as minimal and straightforward as
possible to allow easy customization and control. [Learn
more](../custom-components/overview).
## Project Structure
The Admin Panel serves as the entire HTTP layer for Payload, providing a full CRUD interface for your app. This means that both the [REST](../rest-api/overview) and [GraphQL](../graphql/overview) APIs are simply [Next.js Routes](https://nextjs.org/docs/app/building-your-application/routing) that exist directly alongside your front-end application.
Once you [install Payload](../getting-started/installation), the following files and directories will be created in your app:
```plaintext
app
├─ (payload)
├── admin
├─── [[...segments]]
├──── page.tsx
├──── not-found.tsx
├── api
├─── [...slug]
├──── route.ts
├── graphql
├──── route.ts
├── graphql-playground
├──── route.ts
├── custom.scss
├── layout.tsx
```
If you are not familiar with Next.js project structure, you can [learn more
about it here](https://nextjs.org/docs/getting-started/project-structure).
As shown above, all Payload routes are nested within the `(payload)` route group. This creates a boundary between the Admin Panel and the rest of your application by scoping all layouts and styles. The `layout.tsx` file within this directory, for example, is where Payload manages the `html` tag of the document to set proper [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and [`dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes, etc.
The `admin` directory contains all the _pages_ related to the interface itself, whereas the `api` and `graphql` directories contains all the _routes_ related to the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). All admin routes are [easily configurable](#customizing-routes) to meet your application's exact requirements.
**Note:** If you don't intend to use the Admin Panel, [REST
API](../rest-api/overview), or [GraphQL API](../graphql/overview), you can
opt-out by simply deleting their corresponding directories within your Next.js
app. The overhead, however, is completely constrained to these routes, and
will not slow down or affect Payload outside when not in use.
Finally, the `custom.scss` file is where you can add or override globally-oriented styles in the Admin Panel, such as modify the color palette. Customizing the look and feel through CSS alone is a powerful feature of the Admin Panel, [more on that here](./customizing-css).
All auto-generated files will contain the following comments at the top of each file:
```tsx
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */,
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
```
## Admin Options
All root-level options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` property:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
// highlight-start
admin: {
// ...
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| `autoRefresh` | Used to automatically refresh user tokens for users logged into the dashboard. [More details](../authentication/overview). |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `meta` | Base metadata to use for the Admin Panel. [More details](./metadata). |
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| `suppressHydrationWarning` | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `` tag. Defaults to `false`. |
| `theme` | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| `timezones` | Configure the timezone settings for the admin panel. [More details](#timezones) |
| `toast` | Customize the handling of toast messages within the Admin Panel. [More details](#toasts) |
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
also customize [Collection Admin
Options](../configuration/collections#admin-options) and [Global Admin
Options](../configuration/globals#admin-options) through their respective
`admin` keys.
### The Admin User Collection
To specify which Collection to allow to login to the Admin Panel, pass the `admin.user` key equal to the slug of any auth-enabled Collection:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
user: 'admins', // highlight-line
},
})
```
**Important:**
The Admin Panel can only be used by a single auth-enabled Collection. To enable authentication for a Collection, simply set `auth: true` in the Collection's configuration. See [Authentication](../authentication/overview) for more information.
By default, if you have not specified a Collection, Payload will automatically provide a `User` Collection with access to the Admin Panel. You can customize or override the fields and settings of the default `User` Collection by adding your own Collection with `slug: 'users'`. Doing this will force Payload to use your provided `User` Collection instead of its default version.
You can use whatever Collection you'd like to access the Admin Panel as long as the Collection supports [Authentication](../authentication/overview). It doesn't need to be called `users`. For example, you may wish to have two Collections that both support authentication:
- `admins` - meant to have a higher level of permissions to manage your data and access the Admin Panel
- `customers` - meant for end users of your app that should not be allowed to log into the Admin Panel
To do this, specify `admin: { user: 'admins' }` in your config. This will provide access to the Admin Panel to only `admins`. Any users authenticated as `customers` will be prevented from accessing the Admin Panel. See [Access Control](../access-control/overview) for full details.
### Role-based Access Control
It is also possible to allow multiple user types into the Admin Panel with limited permissions, known as role-based access control (RBAC). For example, you may wish to have two roles within the `admins` Collection:
- `super-admin` - full access to the Admin Panel to perform any action
- `editor` - limited access to the Admin Panel to only manage content
To do this, add a `roles` or similar field to your auth-enabled Collection, then use the `access.admin` property to grant or deny access based on the value of that field. See [Access Control](../access-control/overview) for full details. For a complete, working example of role-based access control, check out the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth).
## Customizing Routes
You have full control over the routes that Payload binds itself to. This includes both [Root-level Routes](#root-level-routes) such as the [REST API](../rest-api/overview), and [Admin-level Routes](#admin-level-routes) such as the user's account page. You can customize these routes to meet the needs of your application simply by specifying the desired paths in your config.
### Root-level Routes
Root-level routes are those that are not behind the `/admin` path, such as the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview), or the root path of the Admin Panel itself.
To customize root-level routes, use the `routes` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/custom-admin-route', // highlight-line
},
})
```
The following options are available:
| Option | Default route | Description |
| ------------------- | --------------------- | ------------------------------------------------- |
| `admin` | `/admin` | The Admin Panel itself. |
| `api` | `/api` | The [REST API](../rest-api/overview) base path. |
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
| `graphQLPlayground` | `/graphql-playground` | The GraphQL Playground. |
**Important:** Changing Root-level Routes also requires a change to [Project
Structure](#project-structure) to match the new route. [More
details](#customizing-root-level-routes).
**Tip:** You can easily add _new_ routes to the Admin Panel through [Custom
Endpoints](../rest-api/overview#custom-endpoints) and [Custom
Views](../custom-components/custom-views).
#### Customizing Root-level Routes
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
This change, however, also requires a change to your [Project Structure](#project-structure) to match the new route.
For example, if you set `routes.admin` to `/`:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/', // highlight-line
},
})
```
Then you would need to completely remove the `admin` directory from the project structure:
```plaintext
app
├─ (payload)
├── [[...segments]]
├──── ...
├── layout.tsx
```
**Note:** If you set Root-level Routes _before_ auto-generating the Admin
Panel via `create-payload-app`, your [Project Structure](#project-structure)
will already be set up correctly.
### Admin-level Routes
Admin-level routes are those behind the `/admin` path. These are the routes that are part of the Admin Panel itself, such as the user's account page, the login page, etc.
To customize admin-level routes, use the `admin.routes` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
routes: {
account: '/my-account', // highlight-line
},
},
})
```
The following options are available:
| Option | Default route | Description |
| ----------------- | -------------------- | ----------------------------------------- |
| `account` | `/account` | The user's account page. |
| `createFirstUser` | `/create-first-user` | The page to create the first user. |
| `forgot` | `/forgot` | The password reset page. |
| `inactivity` | `/logout-inactivity` | The page to redirect to after inactivity. |
| `login` | `/login` | The login page. |
| `logout` | `/logout` | The logout page. |
| `reset` | `/reset` | The password reset page. |
| `unauthorized` | `/unauthorized` | The unauthorized page. |
**Note:** You can also swap out entire _views_ out for your own, using the
`admin.views` property of the Payload Config. See [Custom
Views](../custom-components/custom-views) for more information.
## I18n
The Payload Admin Panel is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/main/packages/translations). Languages are automatically detected based on the user's browser and used by the Admin Panel to display all text in that language. If no language was detected, or if the user's language is not yet supported, English will be chosen. Users can easily specify their language by selecting one from their account page. See [I18n](../configuration/i18n) for more information.
## Light and Dark Modes
Users in the Admin Panel have the ability to choose between light mode and dark mode for their editing experience. Users can select their preferred theme from their account page. Once selected, it is saved to their user's preferences and persisted across sessions and devices. If no theme was selected, the Admin Panel will automatically detect the operation system's theme and use that as the default.
## Timezones
The `admin.timezones` configuration allows you to configure timezone settings for the Admin Panel. You can customise the available list of timezones and in the future configure the default timezone for the Admin Panel and for all users.
The following options are available:
| Option | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
| `supportedTimezones` | An array of label/value options for selectable timezones where the value is the IANA name eg. `America/Detroit` |
| `defaultTimezone` | The `value` of the default selected timezone. eg. `America/Los_Angeles` |
We validate the supported timezones array by checking the value against the list of IANA timezones supported via the Intl API, specifically `Intl.supportedValuesOf('timeZone')`.
**Important** You must enable timezones on each individual date field via
`timezone: true`. See [Date Fields](../fields/overview#date) for more
information.
For example:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
timezones: {
supportedTimezones: [
{
label: "Europe/Dublin",
value: "Europe/Dublin",
},
{
label: "Europe/Amsterdam",
value: "Europe/Amsterdam",
},
{
label: "Europe/Bucharest",
value: "Europe/Bucharest",
},
],
defaultTimezone: "Europe/Amsterdam",
},
},
})
```
## Toast
The `admin.toast` configuration allows you to customize the handling of toast messages within the Admin Panel, such as increasing the duration they are displayed and limiting the number of visible toasts at once.
**Note:** The Admin Panel currently uses the
[Sonner](https://sonner.emilkowal.ski) library for toast notifications.
The following options are available for the `admin.toast` configuration:
| Option | Description | Default |
| ---------- | ---------------------------------------------------------------------------------------------------------------- | ------- |
| `duration` | The length of time (in milliseconds) that a toast message is displayed. | `4000` |
| `expand` | If `true`, will expand the message stack so that all messages are shown simultaneously without user interaction. | `false` |
| `limit` | The maximum number of toasts that can be visible on the screen at once. | `5` |
# Preview
Source: https://payloadcms.com/docs/admin/preview
Preview is a feature that allows you to generate a direct link to your front-end application. When enabled, a "preview" button will appear on the Edit View within the [Admin Panel](./overview) with an href pointing to the URL you provide. This will provide your editors with a quick way of navigating to the front-end application where that Document's data is represented. Otherwise, they'd have to determine that URL themselves which is not always straightforward especially in complex apps.
The Preview feature can also be used to achieve something known as "Draft Preview". With Draft Preview, you can navigate to your front-end application and enter "draft mode", where your queries are modified to fetch draft content instead of published content. This is useful for seeing how your content will look before being published. [More details](#draft-preview).
**Note:** Preview is different than [Live Preview](../live-preview/overview).
Live Preview loads your app within an iframe and renders it in the Admin Panel
allowing you to see changes in real-time. Preview, on the other hand, allows
you to generate a direct link to your front-end application.
To add Preview, pass a function to the `admin.preview` property in any [Collection Config](../configuration/collections#admin-options) or [Global Config](../configuration/globals#admin-options):
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: ({ slug }) => `http://localhost:3000/${slug}`,
},
fields: [
{
name: 'slug',
type: 'text',
},
],
}
```
## Options
The `preview` function resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path, and can run async if needed.
The following arguments are provided to the `preview` function:
| Path | Description |
| ------------- | ------------------------------------------------------------------------------------------ |
| **`doc`** | The data of the Document being edited. This includes changes that have not yet been saved. |
| **`options`** | An object with additional properties. |
The `options` object contains the following properties:
| Path | Description |
| ------------ | ----------------------------------------------------- |
| **`locale`** | The current locale of the Document being edited. |
| **`req`** | The Payload Request object. |
| **`token`** | The JWT token of the currently authenticated in user. |
If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the `req` property to build this URL:
```ts
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line
```
## Draft Preview
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.
### Next.js
If you're using Next.js, you can do the following code to enter [Draft Mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode).
#### Step 1: Format the Preview URL
First, format your `admin.preview` function to point to a custom endpoint that you'll open on your front-end. This URL should include a few key query search params:
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: ({ slug, collection }) => {
const encodedParams = new URLSearchParams({
slug,
collection,
path: `/${slug}`,
previewSecret: process.env.PREVIEW_SECRET || '',
})
return `/preview?${encodedParams.toString()}` // highlight-line
},
},
fields: [
{
name: 'slug',
type: 'text',
},
],
}
```
#### Step 2: Create the Preview Route
Then, create an API route that verifies the preview secret, authenticates the user, and enters draft mode:
`/app/preview/route.ts`
```ts
import type { CollectionSlug, PayloadRequest } from 'payload'
import { getPayload } from 'payload'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
import configPromise from '@payload-config'
export async function GET(
req: {
cookies: {
get: (name: string) => {
value: string
}
}
} & Request,
): Promise {
const payload = await getPayload({ config: configPromise })
const { searchParams } = new URL(req.url)
const path = searchParams.get('path')
const collection = searchParams.get('collection') as CollectionSlug
const slug = searchParams.get('slug')
const previewSecret = searchParams.get('previewSecret')
if (previewSecret !== process.env.PREVIEW_SECRET) {
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
if (!path || !collection || !slug) {
return new Response('Insufficient search params', { status: 404 })
}
if (!path.startsWith('/')) {
return new Response(
'This endpoint can only be used for relative previews',
{ status: 500 },
)
}
let user
try {
user = await payload.auth({
req: req as unknown as PayloadRequest,
headers: req.headers,
})
} catch (error) {
payload.logger.error(
{ err: error },
'Error verifying token for live preview',
)
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
const draft = await draftMode()
if (!user) {
draft.disable()
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
// You can add additional checks here to see if the user is allowed to preview this page
draft.enable()
redirect(path)
}
```
#### Step 3: Query Draft Content
Finally, in your front-end application, you can detect draft mode and adjust your queries to include drafts:
`/app/[slug]/page.tsx`
```ts
export default async function Page({ params: paramsPromise }) {
const { slug = 'home' } = await paramsPromise
const { isEnabled: isDraftMode } = await draftMode()
const payload = await getPayload({ config })
const page = await payload.find({
collection: 'pages',
depth: 0,
draft: isDraftMode, // highlight-line
limit: 1,
overrideAccess: isDraftMode,
where: {
slug: {
equals: slug,
},
},
})?.then(({ docs }) => docs?.[0])
if (page === null) {
return notFound()
}
return (
{page?.title}
)
}
```
**Note:** For fully working example of this, check of the official [Draft
Preview
Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
in the [Examples
Directory](https://github.com/payloadcms/payload/tree/main/examples).
# Document Locking
Source: https://payloadcms.com/docs/admin/locked-documents
Document locking in Payload ensures that only one user at a time can edit a document, preventing data conflicts and accidental overwrites. When a document is locked, other users are prevented from making changes until the lock is released, ensuring data integrity in collaborative environments.
The lock is automatically triggered when a user begins editing a document within the Admin Panel and remains in place until the user exits the editing view or the lock expires due to inactivity.
## How it works
When a user starts editing a document, Payload locks it for that user. If another user attempts to access the same document, they will be notified that it is currently being edited. They can then choose one of the following options:
- View in Read-Only: View the document without the ability to make any changes.
- Take Over: Take over editing from the current user, which locks the document for the new editor and notifies the original user.
- Return to Dashboard: Navigate away from the locked document and continue with other tasks.
The lock will automatically expire after a set period of inactivity, configurable using the `duration` property in the `lockDocuments` configuration, after which others can resume editing.
**Note:** If your application does not require document locking, you can
disable this feature for any collection or global by setting the
`lockDocuments` property to `false`.
### Config Options
The `lockDocuments` property exists on both the Collection Config and the Global Config. Document locking is enabled by default, but you can customize the lock duration or turn off the feature for any collection or global.
Here's an example configuration for document locking:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
},
// other fields...
],
lockDocuments: {
duration: 600, // Duration in seconds
},
}
```
#### Locking Options
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`lockDocuments`** | Enables or disables document locking for the collection or global. By default, document locking is enabled. Set to an object to configure, or set to false to disable locking. |
| **`duration`** | Specifies the duration (in seconds) for how long a document remains locked without user interaction. The default is 300 seconds (5 minutes). |
### Impact on APIs
Document locking affects both the Local and REST APIs, ensuring that if a document is locked, concurrent users will not be able to perform updates or deletes on that document (including globals). If a user attempts to update or delete a locked document, they will receive an error.
Once the document is unlocked or the lock duration has expired, other users can proceed with updates or deletes as normal.
#### Overriding Locks
For operations like `update` and `delete`, Payload includes an `overrideLock` option. This boolean flag, when set to `false`, enforces document locks, ensuring that the operation will not proceed if another user currently holds the lock.
By default, `overrideLock` is set to `true`, which means that document locks are ignored, and the operation will proceed even if the document is locked. To enforce locks and prevent updates or deletes on locked documents, set `overrideLock: false`.
```ts
const result = await payload.update({
collection: 'posts',
id: '123',
data: {
title: 'New title',
},
overrideLock: false, // Enforces the document lock, preventing updates if the document is locked
})
```
This option is particularly useful in scenarios where administrative privileges or specific workflows require you to override the lock and ensure the operation is completed.
# Customizing CSS & SCSS
Source: https://payloadcms.com/docs/admin/customizing-css
Customizing the Payload [Admin Panel](./overview) through CSS alone is one of the easiest and most powerful ways to customize the look and feel of the dashboard. To allow for this level of customization, Payload:
1. Exposes a [root-level stylesheet](#global-css) for you to inject custom selectors
1. Provides a [CSS library](#css-library) that can be easily overridden or extended
1. Uses [BEM naming conventions](http://getbem.com) so that class names are globally accessible
To customize the CSS within the Admin UI, determine scope and change you'd like to make, and then add your own CSS or SCSS to the configuration as needed.
## Global CSS
Global CSS refers to the CSS that is applied to the entire [Admin Panel](./overview). This is where you can have a significant impact to the look and feel of the Admin UI through just a few lines of code.
You can add your own global CSS through the root `custom.scss` file of your app. This file is loaded into the root of the Admin Panel and can be used to inject custom selectors or styles however needed.
Here is an example of how you might target the Dashboard View and change the background color:
```scss
.dashboard {
background-color: red; // highlight-line
}
```
**Note:** If you are building [Custom
Components](../custom-components/overview), it is best to import your own
stylesheets directly into your components, rather than using the global
stylesheet. You can continue to use the [CSS library](#css-library) as needed.
### Specificity rules
All Payload CSS is encapsulated inside CSS layers under `@layer payload-default`. Any custom css will now have the highest possible specificity.
We have also provided a layer `@layer payload` if you want to use layers and ensure that your styles are applied after payload.
To override existing styles in a way that the previous rules of specificity would be respected you can use the default layer like so
```css
@layer payload-default {
// my styles within the Payload specificity
}
```
## Re-using Payload SCSS variables and utilities
You can re-use Payload's SCSS variables and utilities in your own stylesheets by importing it from the UI package.
```scss
@import '~@payloadcms/ui/scss';
```
## CSS Library
To make it as easy as possible for you to override default styles, Payload uses [BEM naming conventions](http://getbem.com/) for all CSS within the Admin UI. If you provide your own CSS, you can override any built-in styles easily, including targeting nested components and their various component states.
You can also override Payload's built-in [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). These variables are widely consumed by the Admin Panel, so modifying them has a significant impact on the look and feel of the Admin UI.
The following variables are defined and can be overridden:
- [Breakpoints](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/queries.scss)
- [Colors](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/colors.scss)
- Base color shades (white to black by default)
- Success / warning / error color shades
- Theme-specific colors (background, input background, text color, etc.)
- Elevation colors (used to determine how "bright" something should be when compared to the background)
- [Sizing](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/app.scss)
- Horizontal gutter
- Transition speeds
- Font sizes
- Etc.
For an up-to-date, comprehensive list of all available variables, please refer to the [Source Code](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss).
**Warning:** If you're overriding colors or theme elevations, make sure to
consider how [your changes will affect dark mode](#dark-mode).
#### Dark Mode
Colors are designed to automatically adapt to theme of the [Admin Panel](./overview). By default, Payload automatically overrides all `--theme-elevation` colors and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc.
# React Hooks
Source: https://payloadcms.com/docs/admin/react-hooks
Payload provides a variety of powerful [React Hooks](https://react.dev/reference/react-dom/hooks) that can be used within your own [Custom Components](../custom-components/overview), such as [Custom Fields](../fields/overview#custom-components). With them, you can interface with Payload itself to build just about any type of complex customization you can think of.
**Reminder:** All Custom Components are [React Server
Components](https://react.dev/reference/rsc/server-components) by default.
Hooks, on the other hand, are only available in client-side environments. To
use hooks, [ensure your component is a client
component](../custom-components/overview#client-components).
## useField
The `useField` hook is used internally within all field components. It manages sending and receiving a field's state from its parent form. When you build a [Custom Field Component](../fields/overview#custom-components), you will be responsible for sending and receiving the field's `value` to and from the form yourself.
To do so, import the `useField` hook as follows:
```tsx
'use client'
import type { TextFieldClientComponent } from 'payload'
import { useField } from '@payloadcms/ui'
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
const { value, setValue } = useField({ path }) // highlight-line
return (
{path}
{
setValue(e.target.value)
}}
value={value}
/>
)
}
```
The `useField` hook accepts the following arguments:
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `path` | If you do not provide a `path`, `name` will be used instead. This is the path to the field in the form data. |
| `validate` | A validation function executed client-side _before_ submitting the form to the server. Different than [Field-level Validation](../fields/overview#validation) which runs strictly on the server. |
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
The `useField` hook returns the following object:
```ts
type FieldType = {
errorMessage?: string
errorPaths?: string[]
filterOptions?: FilterOptionsResult
formInitializing: boolean
formProcessing: boolean
formSubmitted: boolean
initialValue?: T
path: string
permissions: FieldPermissions
readOnly?: boolean
rows?: Row[]
schemaPath: string
setValue: (val: unknown, disableModifyingForm?: boolean) => void
showError: boolean
valid?: boolean
value: T
}
```
## 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.
**This hook is great for retrieving only certain fields from form state**
because it ensures that it will only cause a rerender when the items that you
ask for change.
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]]`.
```tsx
'use client'
import { useFormFields } from '@payloadcms/ui'
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 The fee is ${(amount.value * feePercentage.value) / 100}
}
}
```
## useAllFormFields
**To retrieve more than one field**, you can use the `useAllFormFields` hook. 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]]`.
**Warning:** Your component will re-render when _any_ field changes, so use
this hook only if you absolutely need to.
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
'use client'
import { useAllFormFields } from '@payloadcms/ui'
import { reduceFieldsToValues, getSiblingData } from 'payload/shared'
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 `useAllFormFields`.
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/ui/src/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_.
**Warning:**
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.
The `useForm` hook returns an object with the following properties:
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { addFieldRow } = useForm()
return (
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
}
],
[
{
value: "**`removeFieldRow`**",
},
{
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: `
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { removeFieldRow } = useForm()
return (
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
}
],
[
{
value: "**`replaceFieldRow`**",
},
{
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: `
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { replaceFieldRow } = useForm()
return (
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
}
],
]}
/>
## useDocumentForm
The `useDocumentForm` hook works the same way as the [useForm](#useform) hook, but it always gives you access to the top-level `Form` of a document. This is useful if you need to access the document's `Form` context from within a child `Form`.
An example where this could happen would be custom components within lexical blocks, as lexical blocks initialize their own child `Form`.
```tsx
'use client'
import { useDocumentForm } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
const { fields: parentDocumentFields } = useDocumentForm()
return (
The document's Form has ${Object.keys(parentDocumentFields).length} fields
)
}
```
## useCollapsible
The `useCollapsible` hook allows you to control parent collapsibles:
| Property | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed. |
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise. |
| **`toggle`** | Toggles the state of the nearest collapsible. |
| **`isWithinCollapsible`** | Determine when you are within another collapsible. |
**Example:**
```tsx
'use client'
import React from 'react'
import { useCollapsible } from '@payloadcms/ui'
const CustomComponent: React.FC = () => {
const { isCollapsed, toggle } = useCollapsible()
return (
I am {isCollapsed ? 'closed' : 'open'}
)
}
```
## useDocumentInfo
The `useDocumentInfo` hook provides information about the current document being edited, including the following:
| Property | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`action`** | The URL attached to the action attribute on the underlying form element, which specifies where to send the form data when the form is submitted. |
| **`apiURL`** | The API URL for the current document. |
| **`collectionSlug`** | The slug of the collection if editing a collection document. |
| **`currentEditor`** | The user currently editing the document. |
| **`docConfig`** | Either the Collection or Global config of the document, depending on what is being edited. |
| **`docPermissions`** | The current document's permissions. Fallback to collection permissions when no id is present. |
| **`documentIsLocked`** | Whether the document is currently locked by another user. [More details](./locked-documents). |
| **`getDocPermissions`** | Method to retrieve document-level permissions. |
| **`getDocPreferences`** | Method to retrieve document-level user preferences. [More details](./preferences). |
| **`globalSlug`** | The slug of the global if editing a global document. |
| **`hasPublishedDoc`** | Whether the document has a published version. |
| **`hasPublishPermission`** | Whether the current user has permission to publish the document. |
| **`hasSavePermission`** | Whether the current user has permission to save the document. |
| **`id`** | If the doc is a collection, its ID will be returned. |
| **`incrementVersionCount`** | Method to increment the version count of the document. |
| **`initialData`** | The initial data of the document. |
| **`isEditing`** | Whether the document is being edited (as opposed to created). |
| **`isInitializing`** | Whether the document info is still initializing. |
| **`isLocked`** | Whether the document is locked. [More details](./locked-documents). |
| **`lastUpdateTime`** | Timestamp of the last update to the document. |
| **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). |
| **`data`** | The saved data of the document. |
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). |
| **`setDocumentTitle`** | Method to set the document title. |
| **`setHasPublishedDoc`** | Method to update whether the document has been published. |
| **`title`** | The title of the document. |
| **`unlockDocument`** | Method to unlock a document. [More details](./locked-documents). |
| **`unpublishedVersionCount`** | The number of unpublished versions of the document. |
| **`updateDocumentEditor`** | Method to update who is currently editing the document. [More details](./locked-documents). |
| **`updateSavedDocumentData`** | Method to update the saved document data. |
| **`uploadStatus`** | Status of any uploads in progress ('idle', 'uploading', or 'failed'). |
| **`versionCount`** | The current version count of the document. |
**Example:**
```tsx
'use client'
import { useDocumentInfo } from '@payloadcms/ui'
const LinkFromCategoryToPosts: React.FC = () => {
// highlight-start
const { id } = useDocumentInfo()
// highlight-end
// id will be undefined on the create form
if (!id) {
return null
}
return (
View posts
)
}
```
## useListQuery
The `useListQuery` hook is used to subscribe to the data, current query, and other properties used within the List View. You can use this hook within any Custom Component rendered within the List View.
```tsx
'use client'
import { useListQuery } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { data, query } = useListQuery()
// highlight-end
// ...
}
```
The `useListQuery` hook returns an object with the following properties:
| Property | Description |
| ------------------------- | -------------------------------------------------------------------------------------- |
| **`data`** | The data that is being displayed in the List View. |
| **`defaultLimit`** | The default limit of items to display in the List View. |
| **`defaultSort`** | The default sort order of items in the List View. |
| **`handlePageChange`** | A method to handle page changes in the List View. |
| **`handlePerPageChange`** | A method to handle per page changes in the List View. |
| **`handleSearchChange`** | A method to handle search changes in the List View. |
| **`handleSortChange`** | A method to handle sort changes in the List View. |
| **`handleWhereChange`** | A method to handle where changes in the List View. |
| **`modified`** | Whether the query has been changed from its [Query Preset](../query-presets/overview). |
| **`query`** | The current query that is being used to fetch the data in the List View. |
## useSelection
The `useSelection` hook provides information on the selected rows in the List view as well as helper methods to simplify selection. The `useSelection` hook returns an object with the following properties:
| Property | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`count`** | The number of currently selected rows. |
| **`getQueryParams`** | A function that generates a query string based on the current selection state and optional additional filtering parameters. |
| **`selectAll`** | An enum value representing the selection range: `'allAvailable'`, `'allInPage'`, `'none'`, and `'some'`. The enum, `SelectAllStatus`, is exported for easier comparisons. |
| **`selected`** | A map of document id keys and boolean values representing their selection status. |
| **`setSelection`** | A function that toggles the selection status of a document row. |
| **`toggleAll`** | A function that toggles selection for all documents on the current page or selects all available documents when passed `true`. |
| **`totalDocs`** | The number of total documents in the collection. |
**Example:**
```tsx
'use client'
import { useSelection } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { count, toggleAll, totalDocs } = useSelection()
// highlight-end
return (
<>
Selected {count} out of {totalDocs} docs!
>
)
}
```
## 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
'use client'
import { useLocale } from '@payloadcms/ui'
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale()
// highlight-end
const trans = {
en: 'Hello',
es: 'Hola',
}
return {trans[locale.code]}
}
```
## 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 affects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
```tsx
'use client'
import { useAuth } from '@payloadcms/ui'
import type { User } from '../payload-types.ts'
const Greeting: React.FC = () => {
// highlight-start
const { user } = useAuth()
// highlight-end
return Hi, {user.email}!
}
```
## useConfig
Used to retrieve the Payload [Client Config](../custom-components/overview#accessing-the-payload-config).
```tsx
'use client'
import { useConfig } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { config } = useConfig()
// highlight-end
return {config.serverURL}
}
```
If you need to retrieve a specific collection or global config by its slug, `getEntityConfig` is the most efficient way to do so:
```tsx
'use client'
import { useConfig } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { getEntityConfig } = useConfig()
const mediaConfig = getEntityConfig({ collectionSlug: 'media' })
// highlight-end
return (
The media collection has {mediaConfig.fields.length} fields.
)
}
```
## 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
'use client'
import { useEditDepth } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const editDepth = useEditDepth()
// highlight-end
return My component is {editDepth} levels deep
}
```
## usePreferences
Returns methods to set and get user preferences. More info can be found [here](../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
'use client'
import { useTheme } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { autoMode, setTheme, theme } = useTheme()
// highlight-end
return (
<>
The current theme is {theme} and autoMode is {autoMode}
>
)
}
```
## useTableColumns
Returns properties and methods to manipulate table columns:
| Property | Description |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| **`columns`** | The current state of columns including their active status and configuration |
| **`LinkedCellOverride`** | A component override for linked cells in the table |
| **`moveColumn`** | A method to reorder columns. Accepts `{ fromIndex: number, toIndex: number }` as arguments |
| **`resetColumnsState`** | A method to reset columns back to their default configuration as defined in the collection config |
| **`setActiveColumns`** | A method to set specific columns to active state while preserving the existing column order. Accepts an array of column names to activate |
| **`toggleColumn`** | A method to toggle a single column's visibility. Accepts a column name as string |
```tsx
'use client'
import { useTableColumns } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { setActiveColumns, resetColumnsState } = useTableColumns()
const activateSpecificColumns = () => {
// Only activates the id and createdAt columns
// Other columns retain their current active/inactive state
// The original column order is preserved
setActiveColumns(['id', 'createdAt'])
}
const resetToDefaults = () => {
// Resets to the default columns defined in the collection config
resetColumnsState()
}
// highlight-end
return (
)
}
```
## 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
'use client'
import { useDocumentEvents } from '@payloadcms/ui'
const ListenForUpdates: React.FC = () => {
const { mostRecentUpdate } = useDocumentEvents()
return {JSON.stringify(mostRecentUpdate)}
}
```
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.
## useStepNav
The `useStepNav` hook provides a way to change the step-nav breadcrumb links in the app header.
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------- |
| **`setStepNav`** | A state setter function which sets the `stepNav` array. |
| **`stepNav`** | A `StepNavItem` array where each `StepNavItem` has a label and optionally a url. |
**Example:**
```tsx
'use client'
import { type StepNavItem, useStepNav } from '@payloadcms/ui'
import { useEffect } from 'react'
export const MySetStepNavComponent: React.FC<{
nav: StepNavItem[]
}> = ({ nav }) => {
const { setStepNav } = useStepNav()
useEffect(() => {
setStepNav(nav)
}, [setStepNav, nav])
return null
}
```
## usePayloadAPI
The `usePayloadAPI` hook is a useful tool for making REST API requests to your Payload instance and handling responses reactively. It allows you to fetch and interact with data while automatically updating when parameters change.
This hook returns an array with two elements:
1. An object containing the API response.
2. A set of methods to modify request parameters.
**Example:**
```tsx
'use client'
import { usePayloadAPI } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// Fetch data from a collection item using its ID
const [{ data, isError, isLoading }, { setParams }] = usePayloadAPI(
'/api/posts/123',
{
initialParams: { depth: 1 },
},
)
if (isLoading) return
Loading...
if (isError) return
Error occurred while fetching data.
return (
{data?.title}
)
}
```
**Arguments:**
| Property | Description |
| ------------- | ----------------------------------------------------------------------------------------------- |
| **`url`** | The API endpoint to fetch data from. Relative URLs will be prefixed with the Payload API route. |
| **`options`** | An object containing initial request parameters and initial state configuration. |
The `options` argument accepts the following properties:
| Property | Description |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| **`initialData`** | Uses this data instead of making an initial request. If not provided, the request runs immediately. |
| **`initialParams`** | Defines the initial parameters to use in the request. Defaults to an empty object `{}`. |
**Returned Value:**
The first item in the returned array is an object containing the following properties:
| Property | Description |
| --------------- | -------------------------------------------------------- |
| **`data`** | The API response data. |
| **`isError`** | A boolean indicating whether the request failed. |
| **`isLoading`** | A boolean indicating whether the request is in progress. |
The second item is an object with the following methods:
| Property | Description |
| --------------- | ----------------------------------------------------------- |
| **`setParams`** | Updates request parameters, triggering a refetch if needed. |
#### Updating Data
The `setParams` function can be used to update the request and trigger a refetch:
```tsx
setParams({ depth: 2 })
```
This is useful for scenarios where you need to trigger another fetch regardless of the `url` argument changing.
## useRouteTransition
Route transitions are useful in showing immediate visual feedback to the user when navigating between pages. This is especially useful on slow networks when navigating to data heavy or process intensive pages.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions by default.
```tsx
import { Link } from '@payloadcms/ui'
const MyComponent = () => {
return Go Somewhere
}
```
You can also trigger route transitions programmatically, such as when using `router.push` from `next/router`. To do this, wrap your function calls with the `startRouteTransition` method provided by the `useRouteTransition` hook.
```ts
'use client'
import React, { useCallback } from 'react'
import { useTransition } from '@payloadcms/ui'
import { useRouter } from 'next/navigation'
const MyComponent: React.FC = () => {
const router = useRouter()
const { startRouteTransition } = useRouteTransition()
const redirectSomewhere = useCallback(() => {
startRouteTransition(() => router.push('/somewhere'))
}, [startRouteTransition, router])
// ...
}
```
# Managing User Preferences
Source: https://payloadcms.com/docs/admin/preferences
As your users interact with the [Admin Panel](./overview), you might want to store their preferences in a persistent manner, so that when they revisit the Admin Panel in a different session or from a different device, they can pick right back up where they left off.
Out of the box, Payload handles the persistence of your users' preferences in a handful of ways, including:
1. Columns in the Collection List View: their active state and order
1. The user's last active [Locale](../configuration/localization)
1. The "collapsed" state of `blocks`, `array`, and `collapsible` fields
1. The last-known state of the `Nav` component, etc.
**Important:**
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.
## 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](../graphql/overview#preferences) and [REST](../rest-api/overview#preferences) 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.
```tsx
'use client'
import React, { Fragment, useState, useEffect, useCallback } from 'react'
import { usePreferences } from '@payloadcms/ui'
const lastUsedColorsPreferenceKey = 'last-used-colors'
export function CustomComponent() {
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 (
{lastUsedColors && (
Last used colors:
{lastUsedColors?.map((color) =>
{color}
)}
)}
)
}
```
# Page Metadata
Source: https://payloadcms.com/docs/admin/metadata
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more. This includes the page title, description, og:image, etc. and requires no additional configuration.
Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views. This allows for the ability to control metadata on any page with high precision, while also providing sensible defaults.
All metadata is injected into Next.js' [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function. This used to generate the `` of pages within the Admin Panel. All metadata options that are available in Next.js are exposed by Payload.
Within the Admin Panel, metadata can be customized at the following levels:
- [Root Metadata](#root-metadata)
- [Collection Metadata](#collection-metadata)
- [Global Metadata](#global-metadata)
- [View Metadata](#view-metadata)
All of these types of metadata share a similar structure, with a few key differences on the Root level. To customize metadata, consult the list of available scopes. Determine the scope that corresponds to what you are trying to accomplish, then author your metadata within the Payload Config accordingly.
## Root Metadata
Root Metadata is the metadata that is applied to all pages within the Admin Panel. This is where you can control things like the suffix appended onto each page's title, the favicon displayed in the browser's tab, and the Open Graph data that is used when sharing the Admin Panel on social media.
To customize Root Metadata, use the `admin.meta` key in your Payload Config:
```ts
{
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Admin Panel',
description: 'The best admin panel in the world',
icons: [
{
rel: 'icon',
type: 'image/png',
url: '/favicon.png',
},
],
},
},
}
```
The following options are available for Root Metadata:
| Key | Type | Description |
| -------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultOGImageType` | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| `titleSuffix` | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
| `[keyof Metadata]` | `unknown` | Any other properties that Next.js supports within the `generateMetadata` function. [More details](https://nextjs.org/docs/app/api-reference/functions/generate-metadata). |
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
also customize metadata on the [Collection](../configuration/collections),
[Global](../configuration/globals), and Document levels through their
respective configs.
### Icons
The Icons Config corresponds to the `` tags that are used to specify icons for the Admin Panel. The `icons` key is an array of objects, each of which represents an individual icon. Icons are differentiated from one another by their `rel` attribute, which specifies the relationship between the document and the icon.
The most common icon type is the favicon, which is displayed in the browser tab. This is specified by the `rel` attribute `icon`. Other common icon types include `apple-touch-icon`, which is used by Apple devices when the Admin Panel is saved to the home screen, and `mask-icon`, which is used by Safari to mask the Admin Panel icon.
To customize icons, use the `admin.meta.icons` property in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
icons: [
// highlight-end
{
rel: 'icon',
type: 'image/png',
url: '/favicon.png',
},
{
rel: 'apple-touch-icon',
type: 'image/png',
url: '/apple-touch-icon.png',
},
],
},
},
}
```
For a full list of all available Icon options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#icons).
### Open Graph
Open Graph metadata is a set of tags that are used to control how URLs are displayed when shared on social media platforms. Open Graph metadata is automatically generated by Payload, but can be customized at the Root level.
To customize Open Graph metadata, use the `admin.meta.openGraph` property in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
openGraph: {
// highlight-end
description: 'The best admin panel in the world',
images: [
{
url: 'https://example.com/image.jpg',
width: 800,
height: 600,
},
],
siteName: 'Payload',
title: 'My Admin Panel',
},
},
},
}
```
For a full list of all available Open Graph options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#opengraph).
### Robots
Setting the `robots` property will allow you to control the `robots` meta tag that is rendered within the `` of the Admin Panel. This can be used to control how search engines index pages and displays them in search results.
By default, the Admin Panel is set to prevent search engines from indexing pages within the Admin Panel.
To customize the Robots Config, use the `admin.meta.robots` property in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
robots: 'noindex, nofollow',
// highlight-end
},
},
}
```
For a full list of all available Robots options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#robots).
##### Prevent Crawling
While setting meta tags via `admin.meta.robots` can prevent search engines from _indexing_ web pages, it does not prevent them from being _crawled_.
To prevent your pages from being crawled altogether, add a `robots.txt` file to your root directory.
```text
User-agent: *
Disallow: /admin/
```
**Note:** If you've customized the path to your Admin Panel via
`config.routes`, be sure to update the `Disallow` directive to match your
custom path.
## Collection Metadata
Collection Metadata is the metadata that is applied to all pages within any given Collection within the Admin Panel. This metadata is used to customize the title and description of all views within any given Collection, unless overridden by the view itself.
To customize Collection Metadata, use the `admin.meta` key within your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Collection',
description: 'The best collection in the world',
},
},
}
```
The Collection Meta config has the same options as the [Root Metadata](#root-metadata) config.
## Global Metadata
Global Metadata is the metadata that is applied to all pages within any given Global within the Admin Panel. This metadata is used to customize the title and description of all views within any given Global, unless overridden by the view itself.
To customize Global Metadata, use the `admin.meta` key within your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Global',
description: 'The best admin panel in the world',
},
},
}
```
The Global Meta config has the same options as the [Root Metadata](#root-metadata) config.
## View Metadata
View Metadata is the metadata that is applied to specific [Views](../custom-components/custom-views) within the Admin Panel. This metadata is used to customize the title and description of a specific view, overriding any metadata set at the [Root](#root-metadata), [Collection](#collection-metadata), or [Global](#global-metadata) level.
To customize View Metadata, use the `meta` key within your View Config:
```ts
{
// ...
admin: {
views: {
dashboard: {
// highlight-start
meta: {
// highlight-end
title: 'My Dashboard',
description: 'The best dashboard in the world',
}
},
},
},
}
```
# Swap in your own React components
Source: https://payloadcms.com/docs/custom-components/overview
The Payload [Admin Panel](../admin/overview) is designed to be as minimal and straightforward as possible to allow for easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default. This enables the use of the [Local API](../local-api/overview) directly on the front-end. Custom Components are available for nearly every part of the Admin Panel for extreme granularity and control.
**Note:** Client Components continue to be fully supported. To use Client
Components in your app, simply include the `'use client'` directive. Payload
will automatically detect and remove all
[non-serializable](https://react.dev/reference/rsc/use-client#serializable-types)
default props before rendering your component. [More
details](#client-components).
There are four main types of Custom Components in Payload:
- [Root Components](./root-components)
- [Collection Components](../configuration/collections#custom-components)
- [Global Components](../configuration/globals#custom-components)
- [Field Components](../fields/overview#custom-components)
To swap in your own Custom Component, first determine the scope that corresponds to what you are trying to accomplish, consult the list of available components, then [author your React component(s)](#building-custom-components) accordingly.
## Defining Custom Components
As Payload compiles the Admin Panel, it checks your config for Custom Components. When detected, Payload either replaces its own default component with yours, or if none exists by default, renders yours outright. While there are many places where Custom Components are supported in Payload, each is defined in the same way using [Component Paths](#component-paths).
To add a Custom Component, point to its file path in your Payload Config:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
Button: '/src/components/Logout#MyComponent', // highlight-line
},
},
},
})
```
**Note:** All Custom Components can be either Server Components or Client
Components, depending on the presence of the `'use client'` directive at the
top of the file.
### Component Paths
In order to ensure the Payload Config is fully Node.js compatible and as lightweight as possible, components are not directly imported into your config. Instead, they are identified by their file path for the Admin Panel to resolve on its own.
Component Paths, by default, are relative to your project's base directory. This is either your current working directory, or the directory specified in `config.admin.importMap.baseDir`.
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, this can be omitted.
```ts
import { buildConfig } from 'payload'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'), // highlight-line
},
components: {
logout: {
Button: '/components/Logout#MyComponent', // highlight-line
},
},
},
})
```
In this example, we set the base directory to the `src` directory, and omit the `/src/` part of our component path string.
### Component Config
While Custom Components are usually defined as a string, you can also pass in an object with additional options:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
// highlight-start
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
},
// highlight-end
},
},
},
})
```
The following options are available:
| Property | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `clientProps` | Props to be passed to the Custom Components if it's a Client Component. [More details](#custom-props). |
| `exportName` | Instead of declaring named exports using `#` in the component path, you can also omit them from `path` and pass them in here. |
| `path` | File path to the Custom Component. Named exports can be appended to the end of the path, separated by a `#`. |
| `serverProps` | Props to be passed to the Custom Component if it's a Server Component. [More details](#custom-props). |
For details on how to build Custom Components, see [Building Custom Components](#building-custom-components).
### Import Map
In order for Payload to make use of [Component Paths](#component-paths), an "Import Map" is automatically generated at either `src/app/(payload)/admin/importMap.js` or `app/(payload)/admin/importMap.js`. This file contains every Custom Component in your config, keyed to their respective paths. When Payload needs to lookup a component, it uses this file to find the correct import.
The Import Map is automatically regenerated at startup and whenever Hot Module Replacement (HMR) runs, or you can run `payload generate:importmap` to manually regenerate it.
#### Overriding Import Map Location
Using the `config.admin.importMap.importMapFile` property, you can override the location of the import map. This is useful if you want to place the import map in a different location, or if you want to use a custom file name.
```ts
import { buildConfig } from 'payload'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'),
importMapFile: path.resolve(
dirname,
'app',
'(payload)',
'custom-import-map.js',
), // highlight-line
},
},
})
```
#### Custom Imports
If needed, custom items can be appended onto the Import Map. This is mostly only relevant for plugin authors who need to add a custom import that is not referenced in a known location.
To add a custom import to the Import Map, use the `admin.dependencies` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
dependencies: {
myTestComponent: {
// myTestComponent is the key - can be anything
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
test: 'hello',
},
},
},
},
})
```
## Building Custom Components
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default. This enables the use of the [Local API](../local-api/overview) directly on the front-end, among other things.
### Default Props
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself.
Here is an example:
```tsx
import React from 'react'
import type { Payload } from 'payload'
async function MyServerComponent({
payload, // highlight-line
}: {
payload: Payload
}) {
const page = await payload.findByID({
collection: 'pages',
id: '123',
})
return
{page.title}
}
```
Each Custom Component receives the following props by default:
| Prop | Description |
| --------- | ------------------------------------------- |
| `payload` | The [Payload](../local-api/overview) class. |
| `i18n` | The [i18n](../configuration/i18n) object. |
**Reminder:** All Custom Components also receive various other props that are
specific to the component being rendered. See [Root
Components](#root-components), [Collection
Components](../configuration/collections#custom-components), [Global
Components](../configuration/globals#custom-components), or [Field
Components](../fields/overview#custom-components) for a complete list of all
default props per component.
### Custom Props
It is also possible to pass custom props to your Custom Components. To do this, you can use either the `clientProps` or `serverProps` properties depending on whether your prop is [serializable](https://react.dev/reference/rsc/use-client#serializable-types), and whether your component is a Server or Client Component.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!', // highlight-line
},
},
},
},
},
})
```
Here is how your component might receive this prop:
```tsx
import React from 'react'
import { Link } from '@payloadcms/ui'
export function MyComponent({ myCustomProp }: { myCustomProp: string }) {
return {myCustomProp}
}
```
### Client Components
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, however, it is possible to use [Client Components](https://react.dev/reference/rsc/use-client) by simply adding the `'use client'` directive at the top of your file. Payload will automatically detect and remove all [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) default props before rendering your component.
```tsx
// highlight-start
'use client'
// highlight-end
import React, { useState } from 'react'
export function MyClientComponent() {
const [count, setCount] = useState(0)
return (
)
}
```
**Reminder:** Client Components cannot be passed [non-serializable
props](https://react.dev/reference/rsc/use-client#serializable-types). If you
are rendering your Client Component _from within_ a Server Component, ensure
that its props are serializable.
### Accessing the Payload Config
From any Server Component, the [Payload Config](../configuration/overview) can be accessed directly from the `payload` prop:
```tsx
import React from 'react'
export default async function MyServerComponent({
payload: {
config, // highlight-line
},
}) {
return Go Home
}
```
But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions and more. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/react-hooks#useconfig) hook:
```tsx
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
export function MyClientComponent() {
// highlight-start
const {
config: { serverURL },
} = useConfig()
// highlight-end
return Go Home
}
```
See [Using Hooks](#using-hooks) for more details.
Similarly, all [Field Components](../fields/overview#custom-components) automatically receive their respective Field Config through props.
Within Server Components, this prop is named `field`:
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({
field: { name },
}) => {
return
{`This field's name is ${name}`}
}
```
Within Client Components, this prop is named `clientField` because its non-serializable props have been removed:
```tsx
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldClientComponent = ({
clientField: { name },
}) => {
return
{`This field's name is ${name}`}
}
```
### Getting the Current Language
All Custom Components can support language translations to be consistent with Payload's [I18n](../configuration/i18n). This will allow your Custom Components to display the correct language based on the user's preferences.
To do this, first add your translation resources to the [I18n Config](../configuration/i18n). Then from any Server Component, you can translate resources using the `getTranslation` function from `@payloadcms/translations`.
All Server Components automatically receive the `i18n` object as a prop by default:
```tsx
import React from 'react'
import { getTranslation } from '@payloadcms/translations'
export default async function MyServerComponent({ i18n }) {
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
return
{translatedTitle}
}
```
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
export function MyClientComponent() {
const { t, i18n } = useTranslation() // highlight-line
return (
{t('namespace1:key', { variable: 'value' })}
{t('namespace2:key', { variable: 'value' })}
{i18n.language}
)
}
```
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
### Getting the Current Locale
All [Custom Views](./custom-views) can support multiple locales to be consistent with Payload's [Localization](../configuration/localization) feature. This can be used to scope API requests, etc.
All Server Components automatically receive the `locale` object as a prop by default:
```tsx
import React from 'react'
export default async function MyServerComponent({ payload, locale }) {
const localizedPage = await payload.findByID({
collection: 'pages',
id: '123',
locale,
})
return
{localizedPage.title}
}
```
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
function Greeting() {
const locale = useLocale() // highlight-line
const trans = {
en: 'Hello',
es: 'Hola',
}
return {trans[locale.code]}
}
```
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
### Using Hooks
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/react-hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export function MyClientComponent() {
const { slug } = useDocumentInfo() // highlight-line
return
{`Entity slug: ${slug}`}
}
```
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
### Adding Styles
Payload has a robust [CSS Library](../admin/customizing-css) that you can use to style your Custom Components to match to Payload's built-in styling. This will ensure that your Custom Components integrate well into the existing design system. This will make it so they automatically adapt to any theme changes that might occur.
To apply custom styles, simply import your own `.css` or `.scss` file into your Custom Component:
```tsx
import './index.scss'
export function MyComponent() {
return
My Custom Component
}
```
Then to colorize your Custom Component's background, for example, you can use the following CSS:
```scss
.my-component {
background-color: var(--theme-elevation-500);
}
```
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
```scss
@import '~@payloadcms/ui/scss';
.my-component {
@include mid-break {
background-color: var(--theme-elevation-900);
}
}
```
**Note:** You can also drill into Payload's own component styles, or easily
apply global, app-wide CSS. More on that [here](../admin/customizing-css).
## Performance
An often overlooked aspect of Custom Components is performance. If unchecked, Custom Components can lead to slow load times of the Admin Panel and ultimately a poor user experience.
This is different from front-end performance of your public-facing site.
For more performance tips, see the [Performance
documentation](../performance/overview).
### Follow React and Next.js best practices
All Custom Components are built using [React](https://react.dev). For this reason, it is important to follow React best practices. This includes using memoization, streaming, caching, optimizing renders, using hooks appropriately, and more.
To learn more, see the [React documentation](https://react.dev/learn).
The Admin Panel itself is a [Next.js](https://nextjs.org) application. For this reason, it is _also_ important to follow Next.js best practices. This includes bundling, when to use layouts vs pages, where to place the server/client boundary, and more.
To learn more, see the [Next.js documentation](https://nextjs.org/docs).
### Reducing initial HTML size
With Server Components, be aware of what is being sent to through the server/client boundary. All props are serialized and sent through the network. This can lead to large HTML sizes and slow initial load times if too much data is being sent to the client.
To minimize this, you must be explicit about what props are sent to the client. Prefer server components and only send the necessary props to the client. This will also offset some of the JS execution to the server.
**Tip:** Use [React Suspense](https://react.dev/reference/react/Suspense) to
progressively load components and improve perceived performance.
### Prevent unnecessary re-renders
If subscribing your component to form state, it may be re-rendering more often than necessary.
To do this, use the [`useFormFields`](../admin/react-hooks) hook instead of `useFields` when you only need to access specific fields.
```ts
'use client'
import { useFormFields } from '@payloadcms/ui'
const MyComponent: TextFieldClientComponent = ({ path }) => {
const value = useFormFields(([fields, dispatch]) => fields[path])
// ...
}
```
# Root Components
Source: https://payloadcms.com/docs/custom-components/root-components
Root Components are those that affect the [Admin Panel](../admin/overview) at a high-level, such as the logo or the main nav. You can swap out these components with your own [Custom Components](./overview) to create a completely custom look and feel.
When combined with [Custom CSS](../admin/customizing-css), you can create a truly unique experience for your users, such as white-labeling the Admin Panel to match your brand.
To override Root Components, use the `admin.components` property at the root of your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
## Config Options
The following options are available:
| Path | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `actions` | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. [More details](#actions). |
| `afterDashboard` | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. [More details](#afterdashboard). |
| `afterLogin` | An array of Custom Components to inject into the built-in Login, _after_ the default login form. [More details](#afterlogin). |
| `afterNavLinks` | An array of Custom Components to inject into the built-in Nav, _after_ the links. [More details](#afternavlinks). |
| `beforeDashboard` | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. [More details](#beforedashboard). |
| `beforeLogin` | An array of Custom Components to inject into the built-in Login, _before_ the default login form. [More details](#beforelogin). |
| `beforeNavLinks` | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. [More details](#beforenavlinks). |
| `graphics.Icon` | The simplified logo used in contexts like the `Nav` component. [More details](#graphicsicon). |
| `graphics.Logo` | The full logo used in contexts like the `Login` view. [More details](#graphicslogo). |
| `header` | An array of Custom Components to be injected above the Payload header. [More details](#header). |
| `logout.Button` | The button displayed in the sidebar that logs the user out. [More details](#logoutbutton). |
| `Nav` | Contains the sidebar / mobile menu in its entirety. [More details](#nav). |
| `settingsMenu` | An array of Custom Components to inject into a popup menu accessible via a gear icon above the logout button. [More details](#settingsMenu). |
| `providers` | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](./custom-providers). |
| `views` | Override or create new views within the Admin Panel. [More details](./custom-views). |
_For details on how to build Custom Components, see [Building Custom Components](./overview#building-custom-components)._
**Note:** You can also use set [Collection
Components](../configuration/collections#custom-components) and [Global
Components](../configuration/globals#custom-components) in their respective
configs.
## Components
### actions
Actions are rendered within the header of the Admin Panel. Actions are typically used to display buttons that add additional interactivity and functionality, although they can be anything you'd like.
To add an action, use the `actions` property in your `admin.components` config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
actions: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple Action component:
```tsx
export default function MyCustomAction() {
return (
)
}
```
**Note:** You can also use add Actions to the [Edit View](./edit-view) and
[List View](./list-view) in their respective configs.
### beforeDashboard
The `beforeDashboard` property allows you to inject Custom Components into the built-in Dashboard, before the default dashboard contents.
To add `beforeDashboard` components, use the `admin.components.beforeDashboard` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeDashboard: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `beforeDashboard` component:
```tsx
export default function MyBeforeDashboardComponent() {
return
This is a custom component injected before the Dashboard.
}
```
### afterDashboard
Similar to `beforeDashboard`, the `afterDashboard` property allows you to inject Custom Components into the built-in Dashboard, _after_ the default dashboard contents.
To add `afterDashboard` components, use the `admin.components.afterDashboard` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterDashboard: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `afterDashboard` component:
```tsx
export default function MyAfterDashboardComponent() {
return
This is a custom component injected after the Dashboard.
}
```
### beforeLogin
The `beforeLogin` property allows you to inject Custom Components into the built-in Login view, _before_ the default login form.
To add `beforeLogin` components, use the `admin.components.beforeLogin` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeLogin: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `beforeLogin` component:
```tsx
export default function MyBeforeLoginComponent() {
return
This is a custom component injected before the Login form.
}
```
### afterLogin
Similar to `beforeLogin`, the `afterLogin` property allows you to inject Custom Components into the built-in Login view, _after_ the default login form.
To add `afterLogin` components, use the `admin.components.afterLogin` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterLogin: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `afterLogin` component:
```tsx
export default function MyAfterLoginComponent() {
return
This is a custom component injected after the Login form.
}
```
### beforeNavLinks
The `beforeNavLinks` property allows you to inject Custom Components into the built-in [Nav Component](#nav), _before_ the nav links themselves.
To add `beforeNavLinks` components, use the `admin.components.beforeNavLinks` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `beforeNavLinks` component:
```tsx
export default function MyBeforeNavLinksComponent() {
return
This is a custom component injected before the Nav links.
}
```
### afterNavLinks
Similar to `beforeNavLinks`, the `afterNavLinks` property allows you to inject Custom Components into the built-in Nav, _after_ the nav links.
To add `afterNavLinks` components, use the `admin.components.afterNavLinks` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `afterNavLinks` component:
```tsx
export default function MyAfterNavLinksComponent() {
return
This is a custom component injected after the Nav links.
}
```
### settingsMenu
The `settingsMenu` property allows you to inject Custom Components into a popup menu accessible via a gear icon in the navigation controls, positioned above the logout button. This is ideal for adding custom actions, utilities, or settings that are relevant at the admin level.
To add `settingsMenu` components, use the `admin.components.settingsMenu` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
settingsMenu: ['/path/to/your/component#ComponentName'],
},
// highlight-end
},
})
```
Here is an example of a simple `settingsMenu` component:
```tsx
'use client'
import { PopupList } from '@payloadcms/ui'
export function MySettingsMenu() {
return (
console.log('Action triggered')}>
Custom Action
window.open('/admin/custom-page')}>
Custom Page
)
}
```
You can pass multiple components to create organized groups of menu items:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
settingsMenu: [
'/components/SystemActions#SystemActions',
'/components/DataManagement#DataManagement',
],
},
},
})
```
### Nav
The `Nav` property contains the sidebar / mobile menu in its entirety. Use this property to completely replace the built-in Nav with your own custom navigation.
To add a custom nav, use the `admin.components.Nav` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
Nav: '/path/to/your/component',
},
// highlight-end
},
})
```
Here is an example of a simple `Nav` component:
```tsx
import { Link } from '@payloadcms/ui'
export default function MyCustomNav() {
return (
)
}
```
### graphics.Icon
The `Icon` property is the simplified logo used in contexts like the `Nav` component. This is typically a small, square icon that represents your brand.
To add a custom icon, use the `admin.components.graphics.Icon` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
graphics: {
Icon: '/path/to/your/component',
},
},
// highlight-end
},
})
```
Here is an example of a simple `Icon` component:
```tsx
export default function MyCustomIcon() {
return
}
```
### graphics.Logo
The `Logo` property is the full logo used in contexts like the `Login` view. This is typically a larger, more detailed representation of your brand.
To add a custom logo, use the `admin.components.graphics.Logo` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
graphics: {
Logo: '/path/to/your/component',
},
},
// highlight-end
},
})
```
Here is an example of a simple `Logo` component:
```tsx
export default function MyCustomLogo() {
return
}
```
### header
The `header` property allows you to inject Custom Components above the Payload header.
Examples of a custom header components might include an announcements banner, a notifications bar, or anything else you'd like to display at the top of the Admin Panel in a prominent location.
To add `header` components, use the `admin.components.header` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
header: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `header` component:
```tsx
export default function MyCustomHeader() {
return (
My Custom Header
)
}
```
### logout.Button
The `logout.Button` property is the button displayed in the sidebar that should log the user out when clicked.
To add a custom logout button, use the `admin.components.logout.Button` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
logout: {
Button: '/path/to/your/component',
},
},
// highlight-end
},
})
```
Here is an example of a simple `logout.Button` component:
```tsx
export default function MyCustomLogoutButton() {
return
}
```
# Swap in your own React Context providers
Source: https://payloadcms.com/docs/custom-components/custom-providers
As you add more and more [Custom Components](./overview) to your [Admin Panel](../admin/overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s) to your app. Payload allows you to inject your own context providers where you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, use } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export function MyProvider({ children }: { children: React.ReactNode }) {
return {children}
}
export const useMyCustomContext = () => use(MyCustomContext)
```
_For details on how to build Custom Components, see [Building Custom Components](./overview#building-custom-components)._
**Reminder:** React Context exists only within Client Components. This means
they must include the `use client` directive at the top of their files and
cannot contain server-only code. To use a Server Component here, simply _wrap_
your Client Component with it.
# Customizing Views
Source: https://payloadcms.com/docs/custom-components/custom-views
Views are the individual pages that make up the [Admin Panel](../admin/overview), such as the Dashboard, [List View](./list-view), and [Edit View](./edit-view). One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](./overview) that can either replace built-in views or be entirely new.
There are four types of views within the Admin Panel:
- [Root Views](#root-views)
- [Collection Views](#collection-views)
- [Global Views](#global-views)
- [Document Views](./document-views)
To swap in your own Custom View, first determine the scope that corresponds to what you are trying to accomplish, consult the list of available components, then [author your React component(s)](#building-custom-views) accordingly.
## Configuration
### Replacing Views
To customize views, use the `admin.components.views` property in your [Payload Config](../configuration/overview). This is an object with keys for each view you want to customize. Each key corresponds to the view you want to customize.
The exact list of available keys depends on the scope of the view you are customizing, depending on whether it's a [Root View](#root-views), [Collection View](#collection-views), or [Global View](#global-views). Regardless of the scope, the principles are the same.
Here is an example of how to swap out a built-in view:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
dashboard: {
Component: '/path/to/MyCustomDashboard',
},
// highlight-end
},
},
},
})
```
For more granular control, pass a configuration object instead. Payload exposes the following properties for each view:
| Property | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Component` \* | Pass in the component path that should be rendered when a user navigates to this route. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. Must begin with a forward slash (`/`). |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| `strict` | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| `sensitive` | When true, will match if the path is case sensitive. |
| `meta` | Page metadata overrides to apply to this view within the Admin Panel. [More details](../admin/metadata). |
_\* An asterisk denotes that a property is required._
### Adding New Views
To add a _new_ view to the [Admin Panel](../admin/overview), simply add your own key to the `views` object. This is true for all view scopes.
New views require at least the `Component` and `path` properties:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
myCustomView: {
Component: '/path/to/MyCustomView#MyCustomViewComponent',
path: '/my-custom-view',
},
// highlight-end
},
},
},
})
```
**Note:** Routes are cascading, so unless explicitly given the `exact`
property, they will match on URLs that simply _start_ with the route's path.
This is helpful when creating catch-all routes in your application.
Alternatively, define your nested route _before_ your parent route.
## Building Custom Views
Custom Views are simply [Custom Components](./overview) rendered at the page-level. Custom Views can either [replace existing views](#replacing-views) or [add entirely new ones](#adding-new-views). The process is generally the same regardless of the type of view you are customizing.
To understand how to build Custom Views, first review the [Building Custom Components](./overview#building-custom-components) guide. Once you have a Custom Component ready, you can use it as a Custom View.
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
Component: '/path/to/MyCustomView', // highlight-line
},
// highlight-end
},
},
},
}
```
### Default Props
Your Custom Views will be provided with the following props:
| Prop | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `initPageResult` | An object containing `req`, `payload`, `permissions`, etc. |
| `clientConfig` | The Client Config object. [More details](./overview#accessing-the-payload-config). |
| `importMap` | The import map object. |
| `params` | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
| `searchParams` | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
| `doc` | The document being edited. Only available in Document Views. [More details](./document-views). |
| `i18n` | The [i18n](../configuration/i18n) object. |
| `payload` | The [Payload](../local-api/overview) class. |
**Note:** Some views may receive additional props, such as [Collection
Views](#collection-views) and [Global Views](#global-views). See the relevant
section for more details.
Here is an example of a Custom View component:
```tsx
import type { AdminViewServerProps } from 'payload'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView(props: AdminViewServerProps) {
return (
Custom Default Root View
This view uses the Default Template.
)
}
```
**Tip:** For consistent layout and navigation, you may want to wrap your
Custom View with one of the built-in [Templates](./overview#templates).
### View Templates
Your Custom Root Views can optionally use one of the templates that Payload provides. The most common of these is the Default Template which provides the basic layout and navigation.
Here is an example of how to use the Default Template in your Custom View:
```tsx
import type { AdminViewServerProps } from 'payload'
import { DefaultTemplate } from '@payloadcms/next/templates'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView({
initPageResult,
params,
searchParams,
}: AdminViewServerProps) {
return (
Custom Default Root View
This view uses the Default Template.
)
}
```
### Securing Custom Views
All Custom Views are public by default. 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.
Here is how you might secure a Custom View:
```tsx
import type { AdminViewServerProps } from 'payload'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView({ initPageResult }: AdminViewServerProps) {
const {
req: { user },
} = initPageResult
if (!user) {
return
You must be logged in to view this page.
}
return (
Custom Default Root View
This view uses the Default Template.
)
}
```
## Root Views
Root Views are the main views of the [Admin Panel](../admin/overview). These are views that are scoped directly under the `/admin` route, such as the Dashboard or Account views.
To [swap out](#replacing-views) Root Views with your own, or to [create entirely new ones](#adding-new-views), use the `admin.components.views` property at the root of your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
dashboard: {
Component: '/path/to/Dashboard',
},
// highlight-end
// Other options include:
// - account
// - [key: string]
// See below for more details
},
},
},
})
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
The following options are available:
| Property | Description |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `account` | The Account view is used to show the currently logged in user's Account page. |
| `dashboard` | The main landing page of the Admin Panel. |
| `[key]` | Any other key can be used to add a completely new Root View. [More details](#adding-new-views). |
## Collection Views
Collection Views are views that are scoped under the `/collections` route, such as the Collection List and Document Edit views.
To [swap out](#replacing-views) Collection Views with your own, or to [create entirely new ones](#adding-new-views), use the `admin.components.views` property of your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomCollectionView',
},
},
// highlight-end
// Other options include:
// - list
// - [key: string]
// See below for more details
},
},
},
}
```
**Reminder:** The `edit` key is comprised of various nested views, known as
Document Views, that relate to the same Collection Document. [More
details](./document-views).
The following options are available:
| Property | Description |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View corresponds to a single Document for any given Collection and consists of various nested views. [More details](./document-views). |
| `list` | The List View is used to show a list of Documents for any given Collection. [More details](#list-view). |
| `[key]` | Any other key can be used to add a completely new Collection View. [More details](#adding-new-views). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
## Global Views
Global Views are views that are scoped under the `/globals` route, such as the Edit View.
To [swap out](#replacing-views) Global Views with your own or [create entirely new ones](#adding-new-views), use the `admin.components.views` property in your [Global Config](../configuration/globals):
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobalConfig: SanitizedGlobalConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomGlobalView',
},
},
// highlight-end
// Other options include:
// - [key: string]
// See below for more details
},
},
},
}
```
**Reminder:** The `edit` key is comprised of various nested views, known as
Document Views, that relate to the same Global Document. [More
details](./document-views).
The following options are available:
| Property | Description |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View represents a single Document for any given Global and consists of various nested views. [More details](./document-views). |
| `[key]` | Any other key can be used to add a completely new Global View. [More details](#adding-new-views). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
# Document Views
Source: https://payloadcms.com/docs/custom-components/document-views
Document Views consist of multiple, individual views that together represent any single [Collection](../configuration/collections) or [Global](../configuration/globals) Document. All Document Views and are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, respectively.
There are a number of default Document Views, such as the [Edit View](./edit-view) and API View, but you can also create [entirely new views](./custom-views#adding-new-views) as needed. All Document Views share a layout and can be given their own tab-based navigation, if desired.
To customize Document Views, use the `admin.components.views.edit[key]` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionOrGlobalConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomEditView',
},
// Other options include:
// - root
// - api
// - versions
// - version
// - [key: string]
// See below for more details
},
// highlight-end
},
},
},
}
```
## Config Options
The following options are available:
| Property | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root` | The Root View overrides all other nested views and routes. No document controls or tabs are rendered when this key is set. [More details](#document-root). |
| `default` | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. [More details](./edit-view). |
| `versions` | The Versions View is used to navigate the version history of a single document. It is rendered within the "Versions" tab. [More details](../versions/overview). |
| `version` | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](../versions/overview). |
| `api` | The API View is used to display the REST API JSON response for a given document. It is rendered within the "API" tab. |
| `livePreview` | The LivePreview view is used to display the Live Preview interface. It is rendered within the "Live Preview" tab. [More details](../live-preview/overview). |
| `[key]` | Any other key can be used to add a completely new Document View. |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
### Document Root
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#document-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
When setting a Document Root, you are responsible for rendering all necessary components and controls, as no document controls or tabs would be rendered. To replace only the Edit View precisely, use the `edit.default` key instead.
To override the Document Root, use the `views.edit.root` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
components: {
views: {
edit: {
// highlight-start
root: {
Component: '/path/to/MyCustomRootComponent', // highlight-line
},
// highlight-end
},
},
},
},
}
```
### Edit View
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
## Document Tabs
Each Document View can be given a tab for navigation, if desired. Tabs are highly configurable, from as simple as changing the label to swapping out the entire component, they can be modified in any way.
To add or customize tabs in the Document View, use the `views.edit.[key].tab` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
components: {
views: {
edit: {
myCustomView: {
Component: '/path/to/MyCustomView',
path: '/my-custom-tab',
// highlight-start
tab: {
Component: '/path/to/MyCustomTabComponent',
},
// highlight-end
},
anotherCustomView: {
Component: '/path/to/AnotherCustomView',
path: '/another-custom-view',
// highlight-start
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
order: '100',
},
// highlight-end
},
},
},
},
},
}
```
**Note:** This applies to _both_ Collections _and_ Globals.
The following options are available for tabs:
| Property | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `label` | The label to display in the tab. |
| `href` | The URL to navigate to when the tab is clicked. This is optional and defaults to the tab's `path`. |
| `order` | The order in which the tab appears in the navigation. Can be set on default and custom tabs. |
| `Component` | The component to render in the tab. This can be a Server or Client component. [More details](#tab-components) |
### Tab Components
If changing the label or href is not enough, you can also replace the entire tab component with your own custom component. This can be done by setting the `tab.Component` property to the path of your custom component.
Here is an example of how to scaffold a custom Document Tab:
#### Server Component
```tsx
import React from 'react'
import type { DocumentTabServerProps } from 'payload'
import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabServerProps) {
return (
This is a custom Document Tab (Server)
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { DocumentTabClientProps } from 'payload'
import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabClientProps) {
return (
This is a custom Document Tab (Client)
)
}
```
# Edit View
Source: https://payloadcms.com/docs/custom-components/edit-view
The Edit View is where users interact with individual [Collection](../configuration/collections) and [Global](../configuration/globals) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form in which submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree.
The Edit View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view.
**Note:** The Edit View is one of many [Document Views](./document-views) in
the Payload Admin Panel. Each Document View is responsible for a different
aspect of the interacting with a single Document.
## Custom Edit View
To swap out the entire Edit View with a [Custom View](./custom-views), use the `views.edit.default` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```tsx
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
edit: {
// highlight-start
default: {
Component: '/path/to/MyCustomEditViewComponent',
},
// highlight-end
},
},
},
},
})
```
Here is an example of a custom Edit View:
#### Server Component
```tsx
import React from 'react'
import type { DocumentViewServerProps } from 'payload'
export function MyCustomServerEditView(props: DocumentViewServerProps) {
return
This is a custom Edit View (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { DocumentViewClientProps } from 'payload'
export function MyCustomClientEditView(props: DocumentViewClientProps) {
return
This is a custom Edit View (Client)
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
## Custom Components
In addition to swapping out the entire Edit View with a [Custom View](./custom-views), you can also override individual components. This allows you to customize specific parts of the Edit View without swapping out the entire view.
**Important:** Collection and Globals are keyed to a different property in the
`admin.components` object have slightly different options. Be sure to use the
correct key for the entity you are working with.
#### Collections
To override Edit View components for a Collection, use the `admin.components.edit` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
edit: {
// ...
},
// highlight-end
},
},
}
```
The following options are available:
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Collection. [More details](#description). |
| `Upload` | A file upload component. [More details](#upload). |
#### Globals
To override Edit View components for Globals, use the `admin.components.elements` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
components: {
// highlight-start
elements: {
// ...
},
// highlight-end
},
},
}
```
The following options are available:
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Global. [More details](#description). |
### SaveButton
The `SaveButton` property allows you to render a custom Save Button in the Edit View.
To add a `SaveButton` component, use the `components.edit.SaveButton` property in your [Collection Config](../configuration/collections) or `components.elements.SaveButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
SaveButton: '/path/to/MySaveButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `SaveButton` component:
#### Server Component
```tsx
import React from 'react'
import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonServerProps } from 'payload'
export function MySaveButton(props: SaveButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonClientProps } from 'payload'
export function MySaveButton(props: SaveButtonClientProps) {
return
}
```
### beforeDocumentControls
The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls.
To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals):
#### Collections
```
export const MyCollection: CollectionConfig = {
admin: {
components: {
edit: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
#### Globals
```
export const MyGlobal: GlobalConfig = {
admin: {
components: {
elements: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `beforeDocumentControls` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeDocumentControlsServerProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsServerProps,
) {
return
This is a custom beforeDocumentControl button (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeDocumentControlsClientProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsClientProps,
) {
return
This is a custom beforeDocumentControl button (Client)
}
```
### editMenuItems
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
#### Config Example
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
components: {
edit: {
// highlight-start
editMenuItems: ['/path/to/CustomEditMenuItem'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `editMenuItems` component:
#### Server Component
```tsx
import React from 'react'
import type { EditMenuItemsServerProps } from 'payload'
export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<>
Custom Edit Menu Item
Another Custom Edit Menu Item - add as many as you need!
>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditViewMenuItemClientProps } from 'payload'
export const EditMenuItems = (props: EditViewMenuItemClientProps) => {
const handleClick = () => {
console.log('Custom button clicked!')
}
return (
Custom Edit Menu Item
Another Custom Edit Menu Item - add as many as you need!
)
}
```
**Styling:** Use Payload's built-in `PopupList.Button` to ensure your menu
items automatically match the default dropdown styles. If you want a different
look, you can customize the appearance by passing your own `className` to
`PopupList.Button`, or use a completely custom button built with a standard
HTML `button` element or any other component that fits your design
preferences.
### SaveDraftButton
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.
To add a `SaveDraftButton` component, use the `components.edit.SaveDraftButton` property in your [Collection Config](../configuration/collections) or `components.elements.SaveDraftButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
SaveDraftButton: '/path/to/MySaveDraftButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `SaveDraftButton` component:
#### Server Component
```tsx
import React from 'react'
import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonServerProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonClientProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonClientProps) {
return
}
```
### PublishButton
The `PublishButton` property allows you to render a custom Publish Button in the Edit View.
To add a `PublishButton` component, use the `components.edit.PublishButton` property in your [Collection Config](../configuration/collections) or `components.elements.PublishButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
PublishButton: '/path/to/MyPublishButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `PublishButton` component:
#### Server Component
```tsx
import React from 'react'
import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonClientProps) {
return
}
```
### PreviewButton
The `PreviewButton` property allows you to render a custom Preview Button in the Edit View.
To add a `PreviewButton` component, use the `components.edit.PreviewButton` property in your [Collection Config](../configuration/collections) or `components.elements.PreviewButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
PreviewButton: '/path/to/MyPreviewButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `PreviewButton` component:
#### Server Component
```tsx
import React from 'react'
import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonServerProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonClientProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonClientProps) {
return
}
```
### Description
The `Description` property allows you to render a custom description of the Collection or Global in the Edit View.
To add a `Description` component, use the `components.edit.Description` property in your [Collection Config](../configuration/collections) or `components.elements.Description` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
Description: '/path/to/MyDescriptionComponent',
// highlight-end
},
},
}
```
**Note:** The `Description` component is shared between the Edit View and the
[List View](./list-view).
Here's an example of a custom `Description` component:
#### Server Component
```tsx
import React from 'react'
import type { ViewDescriptionServerProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionServerProps) {
return
This is a custom description component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { ViewDescriptionClientProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
return
This is a custom description component (Client)
}
```
### Upload
The `Upload` property allows you to render a custom file upload component in the Edit View.
To add an `Upload` component, use the `components.edit.Upload` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
Upload: '/path/to/MyUploadComponent',
// highlight-end
},
},
},
}
```
**Note:** The Upload component is only available for Collections.
Here's an example of a custom `Upload` component:
```tsx
import React from 'react'
export function MyUploadComponent() {
return
}
```
# List View
Source: https://payloadcms.com/docs/custom-components/list-view
The List View is where users interact with a list of [Collection](../configuration/collections) Documents within the [Admin Panel](../admin/overview). This is where they can view, sort, filter, and paginate their documents to find exactly what they're looking for. This is also where users can perform bulk operations on multiple documents at once, such as deleting, editing, or publishing many.
The List View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view.
**Note:** Only [Collections](../configuration/collections) have a List View.
[Globals](../configuration/globals) do not have a List View as they are single
documents.
## Custom List View
To swap out the entire List View with a [Custom View](./custom-views), use the `admin.components.views.list` property in your [Payload Config](../configuration/overview):
```tsx
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
list: '/path/to/MyCustomListView',
// highlight-end
},
},
},
})
```
Here is an example of a custom List View:
#### Server Component
```tsx
import React from 'react'
import type { ListViewServerProps } from 'payload'
import { DefaultListView } from '@payloadcms/ui'
export function MyCustomServerListView(props: ListViewServerProps) {
return
This is a custom List View (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { ListViewClientProps } from 'payload'
export function MyCustomClientListView(props: ListViewClientProps) {
return
This is a custom List View (Client)
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
## Custom Components
In addition to swapping out the entire List View with a [Custom View](./custom-views), you can also override individual components. This allows you to customize specific parts of the List View without swapping out the entire view for your own.
To override List View components for a Collection, use the `admin.components` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
}
```
The following options are available:
| Path | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `beforeList` | An array of custom components to inject before the list of documents in the List View. [More details](#beforelist). |
| `beforeListTable` | An array of custom components to inject before the table of documents in the List View. [More details](#beforelisttable). |
| `afterList` | An array of custom components to inject after the list of documents in the List View. [More details](#afterlist). |
| `afterListTable` | An array of custom components to inject after the table of documents in the List View. [More details](#afterlisttable). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render a description of the Collection. [More details](#description). |
### beforeList
The `beforeList` property allows you to inject custom components before the list of documents in the List View.
To add `beforeList` components, use the `components.beforeList` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
beforeList: ['/path/to/MyBeforeListComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `beforeList` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeListServerProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListServerProps) {
return
This is a custom beforeList component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeListClientProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListClientProps) {
return
This is a custom beforeList component (Client)
}
```
### beforeListTable
The `beforeListTable` property allows you to inject custom components before the table of documents in the List View.
To add `beforeListTable` components, use the `components.beforeListTable` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
beforeListTable: ['/path/to/MyBeforeListTableComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `beforeListTable` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeListTableServerProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableServerProps) {
return
This is a custom beforeListTable component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeListTableClientProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableClientProps) {
return
This is a custom beforeListTable component (Client)
}
```
### afterList
The `afterList` property allows you to inject custom components after the list of documents in the List View.
To add `afterList` components, use the `components.afterList` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
afterList: ['/path/to/MyAfterListComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `afterList` component:
#### Server Component
```tsx
import React from 'react'
import type { AfterListServerProps } from 'payload'
export function MyAfterListComponent(props: AfterListServerProps) {
return
This is a custom afterList component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { AfterListClientProps } from 'payload'
export function MyAfterListComponent(props: AfterListClientProps) {
return
This is a custom afterList component (Client)
}
```
### afterListTable
The `afterListTable` property allows you to inject custom components after the table of documents in the List View.
To add `afterListTable` components, use the `components.afterListTable` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
afterListTable: ['/path/to/MyAfterListTableComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `afterListTable` component:
#### Server Component
```tsx
import React from 'react'
import type { AfterListTableServerProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableServerProps) {
return
This is a custom afterListTable component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { AfterListTableClientProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableClientProps) {
return
This is a custom afterListTable component (Client)
}
```
### Description
The `Description` property allows you to render a custom description of the Collection in the List View.
To add a `Description` component, use the `components.Description` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
Description: '/path/to/MyDescriptionComponent',
// highlight-end
},
},
}
```
**Note:** The `Description` component is shared between the List View and the
[Edit View](./edit-view).
Here's an example of a custom `Description` component:
#### Server Component
```tsx
import React from 'react'
import type { ViewDescriptionServerProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionServerProps) {
return
This is a custom Collection description component (Server)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { ViewDescriptionClientProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
return
This is a custom Collection description component (Client)
}
```
# Authentication Overview
Source: https://payloadcms.com/docs/authentication/overview
Authentication is a critical part of any application. Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the [Admin Panel](../admin/overview), as well as your own external applications, completely eliminating the need for paid, third-party platforms and services.
Here are some common use cases of Authentication in your own applications:
- Customer accounts for an e-commerce app
- User accounts for a SaaS product
- P2P apps or social sites where users need to log in and manage their profiles
- Online games where players need to track their progress over time
When Authentication is enabled on a [Collection](../configuration/collections), Payload injects all necessary functionality to support the entire user flow. This includes all [auth-related operations](./operations) like account creation, logging in and out, and resetting passwords, all [auth-related emails](./email) like email verification and password reset, as well as any necessary UI to manage users from the Admin Panel.
To enable Authentication on a Collection, use the `auth` property in the [Collection Config](../configuration/collections#config-options):
```ts
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
// ...
auth: true, // highlight-line
}
```

_Admin Panel screenshot depicting an Admins Collection with Auth enabled_
## Config Options
Any [Collection](../configuration/collections) can opt-in to supporting Authentication. Once enabled, each Document that is created within the Collection can be thought of as a "user". This enables a complete authentication workflow on your Collection, such as logging in and out, resetting their password, and more.
**Note:** By default, Payload provides an auth-enabled `User` Collection which
is used to access the Admin Panel. [More
details](../admin/overview#the-admin-user-collection).
To enable Authentication on a Collection, use the `auth` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const Admins: CollectionConfig = {
// ...
// 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
}
```
**Tip:** For default auth behavior, set `auth: true`. This is a good starting
point for most applications.
**Note:** Auth-enabled Collections will be automatically injected with the
`hash`, `salt`, and `email` fields. [More
details](../fields/overview#field-names).
The following options are available:
| Option | Description |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`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. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](../authentication/overview#login-with-username) |
| **`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. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`useSessions`** | True by default. Set to `false` to use stateless JWTs for authentication instead of sessions. |
| **`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 details](./email#email-verification). |
### Login With Username
You can allow users to login with their username instead of their email address by setting the `loginWithUsername` property to `true`.
Example:
```ts
{
slug: 'customers',
auth: {
loginWithUsername: true,
},
}
```
Or, you can pass an object with additional options:
```ts
{
slug: 'customers',
auth: {
loginWithUsername: {
allowEmailLogin: true, // default: false
requireEmail: false, // default: false
},
},
}
```
**`allowEmailLogin`**
If set to `true`, users can log in with either their username or email address. If set to `false`, users can only log in with their username.
**`requireEmail`**
If set to `true`, an email address is required when creating a new user. If set to `false`, email is not required upon creation.
## Auto-Login
For testing and demo purposes you may want to skip forcing the user to login in order to access your application. Typically, all users should be required to login, however, you can speed up local development time by enabling auto-login.
To enable auto-login, set the `autoLogin` property in the [Payload Config](../admin/overview#admin-options):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
admin: {
autoLogin:
process.env.NODE_ENV === 'development'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
},
// highlight-end
})
```
**Warning:** The recommended way to use this feature is behind an [Environment
Variable](../configuration/environment-vars). This will ensure it is
_disabled_ in production.
The following options are available:
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| **`username`** | The username of the user to login as |
| **`email`** | The email address of the user to login as |
| **`password`** | The password of the user to login as. This is only needed if `prefillOnly` is set to true |
| **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. |
## Auto-Refresh
Turning this property on will allow users to stay logged in indefinitely while their browser is open and on the admin panel, by automatically refreshing their authentication token before it expires.
To enable auto-refresh for user tokens, set `autoRefresh: true` in the [Payload Config](../admin/overview#admin-options) to:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
admin: {
autoRefresh: true,
},
// highlight-end
})
```
## Operations
All auth-related operations are available via Payload's REST, Local, and GraphQL APIs. These operations are automatically added to your Collection when you enable Authentication. [More details](./operations).
## Strategies
Out of the box Payload ships with three powerful Authentication strategies:
- [HTTP-Only Cookies](./cookies)
- [JSON Web Tokens (JWT)](./jwt)
- [API-Keys](./api-keys)
Each of these strategies can work together or independently. You can also create your own custom strategies to fit your specific needs. [More details](./custom-strategies).
### HTTP-Only Cookies
[HTTP-only cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/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 by JavaScript in the browser**, unlike JWT's. [More details](./cookies).
### JSON Web Tokens
JWT (JSON Web Tokens) can also be utilized to perform authentication. Tokens are generated on `login`, `refresh` and `me` operations and can be attached to future requests to authenticate users. [More details](./jwt).
### API Keys
API Keys can be enabled on auth collections. These are particularly useful when you want to authenticate against Payload from a third party service. [More details](./api-keys).
### Custom Strategies
There are cases where these may not be enough for your application. Payload is extendable by design so you can wire up your own strategy when you need to. [More details](./custom-strategies).
### Access Control
Default auth fields including `email`, `username`, and `password` can be overridden by defining a custom field with the same name in your collection config. This allows you to customize the field — including access control — while preserving the underlying auth functionality. For example, you might want to restrict the `email` field from being updated once it is created, or only allow it to be read by certain user roles. You can achieve this by redefining the field and setting access rules accordingly.
Here's an example of how to restrict access to default auth fields:
```ts
import type { CollectionConfig } from 'payload'
export const Auth: CollectionConfig = {
slug: 'users',
auth: true,
fields: [
{
name: 'email', // or 'username'
type: 'text',
access: {
create: () => true,
read: () => false,
update: () => false,
},
},
{
name: 'password', // this will be applied to all password-related fields including new password, confirm password.
type: 'text',
hidden: true, // needed only for the password field to prevent duplication in the Admin panel
access: {
update: () => false,
},
},
],
}
```
**Note:**
- Access functions will apply across the application — I.e. if `read` access is disabled on `email`, it will not appear in the Admin panel UI or API.
- Restricting `read` on the `email` or `username` disables the **Unlock** action in the Admin panel as this function requires access to a user-identifying field.
- When overriding the `password` field, you may need to include `hidden: true` to prevent duplicate fields being displayed in the Admin panel.
# Authentication Operations
Source: https://payloadcms.com/docs/authentication/operations
Enabling [Authentication](./overview) on a [Collection](../configuration/collections) automatically exposes additional auth-based operations in the [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview).
## 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](../access-control/overview), just as the [Admin Panel](../admin/overview) 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 be 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 a `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',
},
})
```
**Server Functions:** Payload offers a ready-to-use `login` server function
that utilizes the Local API. For integration details and examples, check out
the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
## 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?allSessions=false',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
)
```
**Example GraphQL Mutation**:
```
mutation {
logoutUser(allSessions: false)
}
```
**Server Functions:** Payload provides a ready-to-use `logout` server function
that manages the user's cookie for a seamless logout. For integration details
and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
#### Logging out with sessions enabled
By default, logging out will only end the session pertaining to the JWT that was used to log out with. However, you can pass `allSessions: true` to the logout operation in order to end all sessions for the user logging out.
## 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
}
}
```
**Server Functions:** Payload exports a ready-to-use `refresh` server function
that automatically renews the user's token and updates the associated cookie.
For integration details and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
## 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',
})
```
**Note:** the token you need to pass to the `verifyEmail` function is unique to verification and is not the same as the token that you can retrieve from the `forgotPassword` operation. It can be found on the user document, as a hidden `_verificationToken` field. If you'd like to retrieve this token, you can use the Local API's `find` or `findByID` methods, setting `showHiddenFields: true`.
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to create one for you if the user was created via REST or GraphQL by looking at the incoming `req`. But this is not supported if you are creating the user via the Local API's `payload.create()` method. If this applies to you, and you do not have a `serverURL` set, you may want to override your `verify.generateEmailHTML` function to provide a full URL to link the user to a proper verification page.
## Unlock
If a user locks themselves out and you wish to deliberately unlock them, you can utilize the Unlock operation. The [Admin Panel](../admin/overview) 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`](../access-control/collections#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',
},
})
```
**Example GraphQL Mutation**:
```
mutation {
unlock[collection-singular-label]
}
```
**Example Local API unlock**:
```ts
const result = await payload.unlock({
collection: 'collection-slug',
})
```
## 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 [Admin Panel](../admin/overview) 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](../authentication/email#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
})
```
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to
create one for you if the `forgot-password` operation was triggered via REST
or GraphQL by looking at the incoming `req`. But this is not supported if you
are calling `payload.forgotPassword()` via the Local API. If you do not have a
`serverURL` set, you may want to override your
`auth.forgotPassword.generateEmailHTML` function to provide a full URL to link
the user to a proper reset-password page.
**Tip:**
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.
## 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")
}
```
# Authentication Emails
Source: https://payloadcms.com/docs/authentication/email
[Authentication](./overview) ties directly into the [Email](../email/overview) functionality that Payload provides. This allows you to send emails to users for verification, password resets, and more. While Payload provides default email templates for these actions, you can customize them to fit your brand.
## Email Verification
Email Verification forces users to prove they have access to the email address they can authenticate. This will help to reduce spam accounts and ensure that users are who they say they are.
To enable Email Verification, use the `auth.verify` property on your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
verify: true, // highlight-line
},
}
```
**Tip:** Verification emails are fully customizable. [More
details](#generateemailhtml).
The following options are available:
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users indicating how to validate their account. [More details](#generateemailhtml). |
| **`generateEmailSubject`** | Allows for overriding the subject of the email that is sent to users indicating how to validate their account. [More details](#generateemailsubject). |
#### 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.
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
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
},
},
}
```
**Important:** 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.
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
verify: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`
},
// highlight-end
},
},
}
```
## Forgot Password
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
forgotPassword: {
// highlight-line
// ...
},
},
}
```
The following options are available:
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`expiration`** | Configure how long password reset tokens remain valid, specified in milliseconds. |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users attempting to reset their password. [More details](#generateEmailHTML). |
| **`generateEmailSubject`** | Allows for overriding the subject of the email that is sent to users attempting to reset their password. [More details](#generateEmailSubject). |
#### generateEmailHTML
This function 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.
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
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 `
`
},
// highlight-end
},
},
}
```
**Important:** 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.
**Tip:** 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.
The following arguments are passed to the `generateEmailHTML` function:
| Argument | Description |
| -------- | ----------------------------------------------------------------- |
| `req` | The request object. |
| `token` | The token that is generated for the user to reset their password. |
| `user` | The user document that is attempting to reset their password. |
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
forgotPassword: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`
},
// highlight-end
},
},
}
```
The following arguments are passed to the `generateEmailSubject` function:
| Argument | Description |
| -------- | ------------------------------------------------------------- |
| `req` | The request object. |
| `user` | The user document that is attempting to reset their password. |
# Cookie Strategy
Source: https://payloadcms.com/docs/authentication/cookies
Payload offers the ability to [Authenticate](./overview) via HTTP-only cookies. These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
### Automatic browser inclusion
Modern browsers automatically include `http-only` cookies when making requests directly to URLs—meaning that if you are running your API on `https://example.com`, and you have logged in and visit `https://example.com/test-page`, your browser will automatically include the Payload authentication cookie for you.
### HTTP Authentication
However, if you use `fetch` or similar APIs to retrieve Payload resources from its REST or GraphQL API, you must specify to include credentials (cookies).
Fetch example, including credentials:
```ts
const response = await fetch('http://localhost:3000/api/pages', {
credentials: 'include',
})
const pages = await response.json()
```
For more about including cookies in requests from your app to your Payload API, [read the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
**Tip:** To make sure you have a Payload cookie set properly in your browser
after logging in, you can use the browsers Developer Tools > Application >
Cookies > [your-domain-here]. The Developer tools will still show HTTP-only
cookies.
### CSRF Attacks
CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes many XSS vulnerabilities, however, CSRF attacks can still be possible.
For example, let's say you have a popular app `https://payload-finances.com` that allows users to manage finances, send and receive money. As Payload is using HTTP-only cookies, that means that browsers automatically will include cookies when sending requests to your domain - **no matter what page created the request**.
So, if a user of `https://payload-finances.com` is logged in and is browsing around on the internet, they might stumble onto a page with malicious intent. Let's look at an example:
```ts
// malicious-intent.com
// makes an authenticated request as on your behalf
const maliciousRequest = await fetch(`https://payload-finances.com/api/me`, {
credentials: 'include',
}).then((res) => await res.json())
```
In this scenario, if your cookie was still valid, malicious-intent.com would be able to make requests like the one above on your behalf. This is a CSRF attack.
### CSRF Prevention
Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
```ts
// payload.config.ts
import { buildConfig } from 'payload'
const config = buildConfig({
serverURL: 'https://my-payload-instance.com',
// highlight-start
csrf: [
// whitelist of domains to allow cookie auth from
'https://your-frontend-app.com',
'https://your-other-frontend-app.com',
// `config.serverURL` is added by default if defined
],
// highlight-end
collections: [
// collections here
],
})
export default config
```
#### Cross domain authentication
If your frontend is on a different domain than your Payload API then you will not be able to use HTTP-only cookies for authentication by default as they will be considered third-party cookies by the browser.
There are a few strategies to get around this:
##### 1. Use subdomains
Cookies can cross subdomains without being considered third party cookies, for example if your API is at api.example.com then you can authenticate from example.com.
##### 2. Configure cookies
If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](./overview#config-options) on your authentication collection to achieve the following setup:
```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
```
Configuration example:
```ts
{
slug: 'users',
auth: {
cookies: {
sameSite: 'None',
secure: true,
}
},
fields: [
// your auth fields here
]
},
```
If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
**Good to know:** Setting up `secure: true` will not work if you're developing
on `http://localhost` or any non-https domain. For local development you
should conditionally set this to `false` based on the environment.
# JWT Strategy
Source: https://payloadcms.com/docs/authentication/jwt
Payload offers the ability to [Authenticate](./overview) via JSON Web Tokens (JWT). These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
### Identifying Users Via The Authorization Header
In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request.
Example:
```ts
const user = await fetch('http://localhost:3000/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
password: 'password',
}),
}).then((req) => await req.json())
const request = await fetch('http://localhost:3000', {
headers: {
Authorization: `JWT ${user.token}`,
},
})
```
### Omitting The Token
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponses` to `true` like so:
```ts
import type { CollectionConfig } from 'payload'
export const UsersWithoutJWTs: CollectionConfig = {
slug: 'users-without-jwts',
auth: {
removeTokenFromResponses: true, // highlight-line
},
}
```
## External JWT Validation
When validating Payload-generated JWT tokens in external services, use the processed secret rather than your original secret key:
```ts
import crypto from 'node:crypto'
const secret = crypto
.createHash('sha256')
.update(process.env.PAYLOAD_SECRET)
.digest('hex')
.slice(0, 32)
```
**Note:** Payload processes your secret using SHA-256 hash and takes the first
32 characters. This processed value is what's used for JWT operations, not
your original secret.
# API Key Strategy
Source: https://payloadcms.com/docs/authentication/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. API keys are generated on a user-by-user basis, similar to email and passwords, and are meant to represent a single user.
For example, if you have a third-party service or external app that needs to be able to perform protected actions against Payload, first you need to create a user within Payload, i.e. `dev@thirdparty.com`. From your external application you will need to authenticate with that user, you have two options:
1. Log in each time with that user and receive an expiring token to request with.
1. Generate a non-expiring API key for that user to request with.
**Tip:**
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.
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](../admin/overview) for each document within the collection that allows you to generate an API key for each user in the Collection.
```ts
import type { CollectionConfig } from 'payload'
export const ThirdPartyAccess: CollectionConfig = {
slug: 'third-party-access',
auth: {
useAPIKey: true, // highlight-line
},
fields: [],
}
```
User API keys are encrypted within the database, meaning that if your database is compromised,
your API keys will not be.
**Important:**
If you change your `PAYLOAD_SECRET`, you will need to regenerate your API keys.
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
no longer be valid.
### HTTP Authentication
To authenticate REST or GraphQL API requests using an API key, set the `Authorization` header. The header is case-sensitive and needs the slug of the `auth.useAPIKey` enabled collection, then " API-Key ", followed by the `apiKey` that has been assigned. Payload's built-in middleware will then assign the user document to `req.user` and handle requests with the proper [Access Control](../access-control/overview). By doing this, Payload recognizes the request being made as a request by the user associated with that API key.
**For example, using Fetch:**
```ts
import Users from '../collections/Users'
const response = await fetch('http://localhost:3000/api/pages', {
headers: {
Authorization: `${Users.slug} API-Key ${YOUR_API_KEY}`,
},
})
```
Payload ensures that the same, uniform [Access Control](../access-control/overview) is used across all authentication strategies. This enables you to utilize your existing Access Control configurations with both API keys and the standard email/password authentication. This consistency can aid in maintaining granular control over your API keys.
### API Key Only Auth
If you want to use API keys as the only authentication method for a collection, you can disable the default local strategy by setting `disableLocalStrategy` to `true` on the collection's `auth` property. This will disable the ability to authenticate with email and password, and will only allow for authentication via API key.
```ts
import type { CollectionConfig } from 'payload'
export const ThirdPartyAccess: CollectionConfig = {
slug: 'third-party-access',
auth: {
useAPIKey: true,
disableLocalStrategy: true, // highlight-line
},
}
```
# Custom Strategies
Source: https://payloadcms.com/docs/authentication/custom-strategies
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.
### Creating a strategy
At the core, a strategy is a way to authenticate a user making a request. As of `3.0` we moved away from [Passport](https://www.passportjs.org) in favor of pulling back the curtain and putting you in full control.
A strategy is made up of the following:
| Parameter | Description |
| --------------------- | ------------------------------------------------------------------------- |
| **`name`** \* | The name of your strategy |
| **`authenticate`** \* | A function that takes in the parameters below and returns a user or null. |
The `authenticate` function is passed the following arguments:
| Argument | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`canSetHeaders`** \* | Whether or not the strategy is being executed from a context where response headers can be set. Default is `false`. |
| **`headers`** \* | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
| **`payload`** \* | The Payload class. Useful for authenticating the identifiable information against Payload. |
| **`isGraphQL`** | Whether or not the strategy is being executed within the GraphQL endpoint. Default is `false`. |
### Example Strategy
At its core a strategy simply takes information from the incoming request and returns a user. This is exactly how Payload's built-in strategies function.
Your `authenticate` method should return an object containing a Payload user document and any optional headers that you'd like Payload to set for you when we return a response.
```ts
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
auth: {
disableLocalStrategy: true,
// highlight-start
strategies: [
{
name: 'custom-strategy',
authenticate: async ({ payload, headers }) => {
const usersQuery = await payload.find({
collection: 'users',
where: {
code: {
equals: headers.get('code'),
},
secret: {
equals: headers.get('secret'),
},
},
})
return {
// Send the user with the collection slug back to authenticate,
// or send null if no user should be authenticated
user: usersQuery.docs[0] ? {
collection: 'users',
...usersQuery.docs[0],
} : null,
// Optionally, you can return headers
// that you'd like Payload to set here when
// it returns the response
responseHeaders: new Headers({
'some-header': 'my header value'
})
}
}
}
]
// highlight-end
},
fields: [
{
name: 'code',
type: 'text',
index: true,
unique: true,
},
{
name: 'secret',
type: 'text',
},
]
}
```
# Token Data
Source: https://payloadcms.com/docs/authentication/token-data
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appended to the request for you.
### Defining Token Data
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.
```ts
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
fields: [
{
// will be stored in the JWT
saveToJWT: true,
type: 'select',
name: 'role',
options: ['super-admin', 'user'],
},
{
// the entire object will be stored in the JWT
// tab fields can do the same thing!
saveToJWT: true,
type: 'group',
name: 'group1',
fields: [
{
type: 'text',
name: 'includeField',
},
{
// will be omitted from the JWT
saveToJWT: false,
type: 'text',
name: 'omitField',
},
],
},
{
type: 'group',
name: 'group2',
fields: [
{
// will be stored in the JWT
// but stored at the top level
saveToJWT: true,
type: 'text',
name: 'includeField',
},
{
type: 'text',
name: 'omitField',
},
],
},
],
}
```
**Tip:**
If you wish to use a different key other than the field `name`, you can define `saveToJWT` as a string.
### Using Token Data
This is especially helpful when writing [Hooks](../hooks/overview) and [Access Control](../access-control/overview) that depend on user defined fields.
```ts
import type { CollectionConfig } from 'payload'
export const Invoices: CollectionConfig = {
slug: 'invoices',
access: {
read: ({ req, data }) => {
if (!req?.user) return false
// highlight-start
if ({ req.user?.role === 'super-admin'}) {
return true
}
// highlight-end
return data.owner === req.user.id
}
}
fields: [
{
name: 'owner',
relationTo: 'users'
},
// ... other fields
],
}
```
# Rich Text Editor
Source: https://payloadcms.com/docs/rich-text/overview
This documentation is about our new editor, based on Lexical (Meta's rich text editor). The previous default
editor was based on Slate and is still supported. You can read [its documentation](../rich-text/slate),
or the optional [migration guide](../rich-text/migration) to migrate from Slate to Lexical (recommended).
The editor is the most important property of the [rich text field](../fields/rich-text).
As a key part of Payload, we are proud to offer you the best editing experience you can imagine. With healthy
defaults out of the box, but also with the flexibility to customize every detail: from the “/” menu
and toolbars (whether inline or fixed) to inserting any component or subfield you can imagine.
To use the rich text editor, first you need to install it:
```bash
pnpm 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'
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'
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, rootFeatures }) => [
...defaultFeatures,
LinkFeature({
// Example showing how to customize the built-in fields
// of the Link feature
fields: ({ defaultFields }) => [
...defaultFields,
{
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` can be both an array of features, or a function returning an array of features. The function provides the following props:
| Prop | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`defaultFeatures`** | This opinionated array contains all "recommended" default features. You can see which features are included in the default features in the table below. |
| **`rootFeatures`** | This array contains all features that are enabled in the root richText editor (the one defined in the payload.config.ts). If this field is the root richText editor, or if the root richText editor is not a lexical editor, this array will be empty. |
## Official Features
You can find more information about the official features in our [official features docs](../rich-text/official-features).
## Creating your own, custom Feature
You can find more information about creating your own feature in our [building custom feature docs](../rich-text/custom-features).
## TypeScript
Every single piece of saved data is 100% fully typed within lexical. It provides a type for every single node, which can be imported from `@payloadcms/richtext-lexical` - each type is prefixed with `Serialized`, e.g., `SerializedUploadNode`.
To fully type the entire editor JSON, you can use our `TypedEditorState` helper type, which accepts a union of all possible node types as a generic. We don't provide a type that already contains all possible node types because they depend on which features you have enabled in your editor. Here is an example:
```ts
import type {
SerializedAutoLinkNode,
SerializedBlockNode,
SerializedHorizontalRuleNode,
SerializedLinkNode,
SerializedListItemNode,
SerializedListNode,
SerializedParagraphNode,
SerializedQuoteNode,
SerializedRelationshipNode,
SerializedTextNode,
SerializedUploadNode,
TypedEditorState,
SerializedHeadingNode,
} from '@payloadcms/richtext-lexical'
const editorState: TypedEditorState<
| SerializedAutoLinkNode
| SerializedBlockNode
| SerializedHorizontalRuleNode
| SerializedLinkNode
| SerializedListItemNode
| SerializedListNode
| SerializedParagraphNode
| SerializedQuoteNode
| SerializedRelationshipNode
| SerializedTextNode
| SerializedUploadNode
| SerializedHeadingNode
> = {
root: {
type: 'root',
direction: 'ltr',
format: '',
indent: 0,
version: 1,
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some text. Every property here is fully-typed',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
textFormat: 0,
version: 1,
},
],
},
}
```
Alternatively, you can use the `DefaultTypedEditorState` type, which includes all types for all nodes included in the `defaultFeatures`:
```ts
import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
const editorState: DefaultTypedEditorState = {
root: {
type: 'root',
direction: 'ltr',
format: '',
indent: 0,
version: 1,
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Some text. Every property here is fully-typed',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
textFormat: 0,
version: 1,
},
],
},
}
```
Just like `TypedEditorState`, the `DefaultTypedEditorState` also accepts an optional node type union as a generic. Here, this would **add** the specified node types to the default ones. Example:
```ts
DefaultTypedEditorState
```
This is a type-safe representation of the editor state. If you look at the auto suggestions of a node's `type` property, you will see all the possible node types you can use.
Make sure to only use types exported from `@payloadcms/richtext-lexical`, not from the lexical core packages. We only have control over the types we export and can make sure they're correct, even though the lexical core may export types with identical names.
### Automatic type generation
Lexical does not generate accurate type definitions for your richText fields for you yet - this will be improved in the future. Currently, it only outputs the rough shape of the editor JSON, which you can enhance using type assertions.
## Admin customization
The Rich Text Field editor configuration has an `admin` property with the following options:
| Property | Description |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor. |
| **`hideDraggableBlockElement`** | Set this property to `true` to hide the draggable element that appears when you hover a node in the editor. |
| **`hideAddBlockButton`** | Set this property to `true` to hide the "+" button that appears when you hover a node in the editor. |
### Disable the gutter
You can disable the gutter (the vertical line padding between the editor and the left edge of the screen) by setting the `hideGutter` prop to `true`:
```ts
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
admin: {
hideGutter: true
},
}),
}
```
### Customize the placeholder
You can customize the placeholder (the text that appears in the editor when it's empty) by setting the `placeholder` prop:
```ts
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
admin: {
placeholder: 'Type your content here...'
},
}),
}
```
## Detecting empty editor state
When you first type into a rich text field and subsequently delete everything through the admin panel, its value changes from `null` to a JSON object containing an empty paragraph.
If needed, you can reset the field value to `null` programmatically - for example, by using a custom hook to detect when the editor is empty.
This also applies to fields like `text` and `textArea`, which could be stored as either `null` or an empty value in the database. Since the empty value for richText is a JSON object, checking for emptiness is a bit more involved - so Payload provides a utility for it:
```ts
import { hasText } from '@payloadcms/richtext-lexical/shared'
hasText(richtextData)
```
# Lexical Converters
Source: https://payloadcms.com/docs/rich-text/converters
Richtext fields save data in JSON - this is great for storage and flexibility and allows you to easily to convert it to other formats:
- [Converting JSX](../rich-text/converting-jsx)
- [Converting HTML](../rich-text/converting-html)
- [Converting Plaintext](../rich-text/converting-plaintext)
- [Converting Markdown and MDX](../rich-text/converting-markdown)
## Retrieving the Editor Config
Some converters require access to the Lexical editor config, which defines available features and behaviors. Payload provides multiple ways to obtain the editor config through the `editorConfigFactory` from `@payloadcms/richtext-lexical`.
### Importing the Factory
First, import the necessary utilities:
```ts
import type { SanitizedConfig } from 'payload'
import { editorConfigFactory } from '@payloadcms/richtext-lexical'
// Your Payload Config needs to be available in order to retrieve the default editor config
const config: SanitizedConfig = {} as SanitizedConfig
```
### Option 1: Default Editor Config
If you require the default editor config:
```ts
const defaultEditorConfig = await editorConfigFactory.default({ config })
```
### Option 2: Extract from a Lexical Field
When a lexical field config is available, you can extract the editor config directly:
```ts
const fieldEditorConfig = editorConfigFactory.fromField({
field: config.collections[0].fields[1],
})
```
### Option 3: Create a Custom Editor Config
You can create a custom editor configuration by specifying additional features:
```ts
import { FixedToolbarFeature } from '@payloadcms/richtext-lexical'
const customEditorConfig = await editorConfigFactory.fromFeatures({
config,
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
],
})
```
### Option 4: Extract from an Instantiated Editor
If you've created a global or reusable Lexical editor instance, you can access its configuration. This method is typically less efficient and not recommended:
```ts
const editor = lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
],
})
const instantiatedEditorConfig = await editorConfigFactory.fromEditor({
config,
editor,
})
```
For better efficiency, consider extracting the `features` into a separate variable and using `fromFeatures` instead of this method.
### Example - Retrieving the editor config from an existing field
If you have access to the sanitized collection config, you can access the lexical sanitized editor config, as every lexical richText field returns it. Here is an example how you can retrieve it from another field's afterRead hook:
```ts
import type { CollectionConfig, RichTextField } from 'payload'
import {
editorConfigFactory,
getEnabledNodes,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
export const MyCollection: CollectionConfig = {
slug: 'slug',
fields: [
{
name: 'text',
type: 'text',
hooks: {
afterRead: [
({ siblingFields, value }) => {
const field: RichTextField = siblingFields.find(
(field) => 'name' in field && field.name === 'richText',
) as RichTextField
const editorConfig = editorConfigFactory.fromField({
field,
})
// Now you can use the editor config
return value
},
],
},
},
{
name: 'richText',
type: 'richText',
editor: lexicalEditor(),
},
],
}
```
# Converting JSX
Source: https://payloadcms.com/docs/rich-text/converting-jsx
## Richtext to JSX
To convert richtext to JSX, import the `RichText` component from `@payloadcms/richtext-lexical/react` and pass the richtext content to it:
```tsx
import React from 'react'
import { RichText } from '@payloadcms/richtext-lexical/react'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
return
}
```
The `RichText` component includes built-in converters for common Lexical nodes. You can add or override converters via the `converters` prop for custom blocks, custom nodes, or any modifications you need. See the [website template](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) for a working example.
When fetching data, ensure your `depth` setting is high enough to fully
populate Lexical nodes such as uploads. The JSX converter requires fully
populated data to work correctly.
### Internal Links
By default, Payload doesn't know how to convert **internal** links to JSX, as it doesn't know what the corresponding URL of the internal link is. You'll notice that you get a "found internal link, but internalDocToHref is not provided" error in the console when you try to render content with internal links.
To fix this, you need to pass the `internalDocToHref` prop to `LinkJSXConverter`. This prop is a function that receives the link node and returns the URL of the document.
```tsx
import type {
DefaultNodeTypes,
SerializedLinkNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
type JSXConvertersFunction,
LinkJSXConverter,
RichText,
} from '@payloadcms/richtext-lexical/react'
import React from 'react'
const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
const { relationTo, value } = linkNode.fields.doc!
if (typeof value !== 'object') {
throw new Error('Expected value to be an object')
}
const slug = value.slug
switch (relationTo) {
case 'posts':
return `/posts/${slug}`
case 'categories':
return `/category/${slug}`
case 'pages':
return `/${slug}`
default:
return `/${relationTo}/${slug}`
}
}
const jsxConverters: JSXConvertersFunction = ({
defaultConverters,
}) => ({
...defaultConverters,
...LinkJSXConverter({ internalDocToHref }),
})
export const MyComponent: React.FC<{
lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
return
}
```
### Lexical Blocks
If your rich text includes custom Blocks or Inline Blocks, you must supply custom converters that match each block's slug. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
For example:
```tsx
'use client'
import type { MyInlineBlock, MyNumberBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
type JSXConvertersFunction,
RichText,
} from '@payloadcms/richtext-lexical/react'
import React from 'react'
// Extend the default node types with your custom blocks for full type safety
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode
| SerializedInlineBlockNode
const jsxConverters: JSXConvertersFunction = ({
defaultConverters,
}) => ({
...defaultConverters,
blocks: {
// Each key should match your block's slug
myNumberBlock: ({ node }) =>
{node.fields.number}
,
myTextBlock: ({ node }) => (
{node.fields.text}
),
},
inlineBlocks: {
// Each key should match your inline block's slug
myInlineBlock: ({ node }) => {node.fields.text},
},
})
export const MyComponent: React.FC<{
lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
return
}
```
### Overriding Converters
You can override any of the default JSX converters by passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
Example - overriding the upload node converter to use next/image:
```tsx
'use client'
import type {
DefaultNodeTypes,
SerializedUploadNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
type JSXConvertersFunction,
RichText,
} from '@payloadcms/richtext-lexical/react'
import Image from 'next/image'
import React from 'react'
type NodeTypes = DefaultNodeTypes
// Custom upload converter component that uses next/image
const CustomUploadComponent: React.FC<{
node: SerializedUploadNode
}> = ({ node }) => {
if (node.relationTo === 'uploads') {
const uploadDoc = node.value
if (typeof uploadDoc !== 'object') {
return null
}
const { alt, height, url, width } = uploadDoc
return
}
return null
}
const jsxConverters: JSXConvertersFunction = ({
defaultConverters,
}) => ({
...defaultConverters,
// Override the default upload converter
upload: ({ node }) => {
return
},
})
export const MyComponent: React.FC<{
lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
return
}
```
# Converting HTML
Source: https://payloadcms.com/docs/rich-text/converting-html
## Rich Text to HTML
There are two main approaches to convert your Lexical-based rich text to HTML:
1. **Generate HTML on-demand (Recommended)**: Convert JSON to HTML wherever you need it, on-demand.
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API.
### On-demand
To convert JSON to HTML on-demand, use the `convertLexicalToHTML` function from `@payloadcms/richtext-lexical/html`. Here's an example of how to use it in a React component in your frontend:
```tsx
'use client'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'
import React from 'react'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({ data })
return
}
```
#### Dynamic Population (Advanced)
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
```tsx
'use client'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import React, { useEffect, useState } from 'react'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const [html, setHTML] = useState(null)
useEffect(() => {
async function convert() {
const html = await convertLexicalToHTMLAsync({
data,
populate: getRestPopulateFn({
apiURL: `http://localhost:3000/api`,
}),
})
setHTML(html)
}
void convert()
}, [data])
return html &&
}
```
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
```tsx
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../config.js'
export const MyRSCComponent = async ({
data,
}: {
data: SerializedEditorState
}) => {
const payload = await getPayload({
config,
})
const html = await convertLexicalToHTMLAsync({
data,
populate: await getPayloadPopulateFn({
currentDepth: 0,
depth: 1,
payload,
}),
})
return html &&
}
```
### HTML field
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended, as it creates a column with duplicate content in another format.
Consider using the [on-demand HTML converter above](../rich-text/converting-html#on-demand-recommended) or the [JSX converter](../rich-text/converting-jsx) unless you have a good reason.
```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
import type { MyTextBlock } from '@/payload-types.js'
import type { CollectionConfig } from 'payload'
import {
BlocksFeature,
type DefaultNodeTypes,
lexicalEditor,
lexicalHTMLField,
type SerializedBlockNode,
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor(),
},
lexicalHTMLField({
htmlFieldName: 'nameOfYourRichTextField_html',
lexicalFieldName: 'nameOfYourRichTextField',
}),
{
name: 'customRichText',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [
{
interfaceName: 'MyTextBlock',
slug: 'myTextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
}),
],
}),
},
lexicalHTMLField({
htmlFieldName: 'customRichText_html',
lexicalFieldName: 'customRichText',
// can pass in additional converters or override default ones
converters: (({ defaultConverters }) => ({
...defaultConverters,
blocks: {
myTextBlock: ({ node, providedCSSString }) =>
`
${node.fields.text}
`,
},
})) as HTMLConvertersFunction<
DefaultNodeTypes | SerializedBlockNode
>,
}),
],
}
```
## Blocks to HTML
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToHTML,
type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode
| SerializedInlineBlockNode
const htmlConverters: HTMLConvertersFunction = ({
defaultConverters,
}) => ({
...defaultConverters,
blocks: {
// Each key should match your block's slug
myTextBlock: ({ node, providedCSSString }) =>
`
${node.fields.text}
`,
},
inlineBlocks: {
// Each key should match your inline block's slug
myInlineBlock: ({ node, providedStyleTag }) =>
`${node.fields.text}`,
},
})
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({
converters: htmlConverters,
data,
})
return
}
```
## HTML to Richtext
If you need to convert raw HTML into a Lexical editor state, use `convertHTMLToLexical` from `@payloadcms/richtext-lexical`, along with the [editorConfigFactory to retrieve the editor config](../rich-text/converters#retrieving-the-editor-config):
```ts
import {
convertHTMLToLexical,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
// Make sure you have jsdom and @types/jsdom installed
import { JSDOM } from 'jsdom'
const html = convertHTMLToLexical({
editorConfig: await editorConfigFactory.default({
config, // Your Payload Config
}),
html: '
text
',
JSDOM, // Pass in the JSDOM import; it's not bundled to keep package size small
})
```
# Converting Markdown
Source: https://payloadcms.com/docs/rich-text/converting-markdown
## Richtext to Markdown
If you have access to the Payload Config and the [lexical editor config](../rich-text/converters#retrieving-the-editor-config), you can convert the lexical editor state to Markdown with the following:
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToMarkdown,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
// Your richtext data here
const data: SerializedEditorState = {}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
})
```
### Example - outputting Markdown from the Collection
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import type { CollectionConfig, RichTextField } from 'payload'
import {
convertLexicalToMarkdown,
editorConfigFactory,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor(),
},
{
name: 'markdown',
type: 'textarea',
admin: {
hidden: true,
},
hooks: {
afterRead: [
({ siblingData, siblingFields }) => {
const data: SerializedEditorState =
siblingData['nameOfYourRichTextField']
if (!data) {
return ''
}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: editorConfigFactory.fromField({
field: siblingFields.find(
(field) =>
'name' in field && field.name === 'nameOfYourRichTextField',
) as RichTextField,
}),
})
return markdown
},
],
beforeChange: [
({ siblingData }) => {
// Ensure that the markdown field is not saved in the database
delete siblingData['markdown']
return null
},
],
},
},
],
}
```
## Markdown to Richtext
If you have access to the Payload Config and the [lexical editor config](../rich-text/converters#retrieving-the-editor-config), you can convert Markdown to the lexical editor state with the following:
```ts
import {
convertMarkdownToLexical,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
const lexicalJSON = convertMarkdownToLexical({
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
markdown: '# Hello world\n\nThis is a **test**.',
})
```
## Converting MDX
Payload supports serializing and deserializing MDX content. While Markdown converters are stored on the features, MDX converters are stored on the blocks that you pass to the `BlocksFeature`.
### Defining a Custom Block
Here is an example of a `Banner` block.
This block:
- Renders in the admin UI as a normal Lexical block with specific fields (e.g. type, content).
- Converts to an MDX `Banner` component.
- Can parse that MDX `Banner` back into a Lexical state.
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import type { Block, CollectionConfig, RichTextField } from 'payload'
import {
BlocksFeature,
convertLexicalToMarkdown,
editorConfigFactory,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
const BannerBlock: Block = {
slug: 'Banner',
fields: [
{
name: 'type',
type: 'select',
defaultValue: 'info',
options: [
{ label: 'Info', value: 'info' },
{ label: 'Warning', value: 'warning' },
{ label: 'Error', value: 'error' },
],
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor(),
},
],
jsx: {
/**
* Convert from Lexical -> MDX:
* child content
*/
export: ({ fields, lexicalToMarkdown }) => {
const props: any = {}
if (fields.type) {
props.type = fields.type
}
return {
children: lexicalToMarkdown({ editorState: fields.content }),
props,
}
},
/**
* Convert from MDX -> Lexical:
*/
import: ({ children, markdownToLexical, props }) => {
return {
type: props?.type,
content: markdownToLexical({ markdown: children }),
}
},
},
}
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [BannerBlock],
}),
],
}),
},
{
name: 'markdown',
type: 'textarea',
hooks: {
afterRead: [
({ siblingData, siblingFields }) => {
const data: SerializedEditorState =
siblingData['nameOfYourRichTextField']
if (!data) {
return ''
}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: editorConfigFactory.fromField({
field: siblingFields.find(
(field) =>
'name' in field && field.name === 'nameOfYourRichTextField',
) as RichTextField,
}),
})
return markdown
},
],
beforeChange: [
({ siblingData }) => {
// Ensure that the markdown field is not saved in the database
delete siblingData['markdown']
return null
},
],
},
},
],
}
```
The conversion is done using the `jsx` property of the block. The `export` function is called when converting from lexical to MDX, and the `import` function is called when converting from MDX to lexical.
### Export
The `export` function takes the block field data and the `lexicalToMarkdown` function as arguments. It returns the following object:
| Property | Type | Description |
| ---------- | ------ | ------------------------------------------------------------------ |
| `children` | string | This will be in between the opening and closing tags of the block. |
| `props` | object | This will be in the opening tag of the block. |
### Import
The `import` function provides data extracted from the MDX. It takes the following arguments:
| Argument | Type | Description |
| ---------- | ------ | ------------------------------------------------------------------------------------ |
| `children` | string | This will be the text between the opening and closing tags of the block. |
| `props` | object | These are the props passed to the block, parsed from the opening tag into an object. |
The returning object is equal to the block field data.
# Converting Plaintext
Source: https://payloadcms.com/docs/rich-text/converting-plaintext
## Richtext to Plaintext
Here's how you can convert richtext data to plaintext using `@payloadcms/richtext-lexical/plaintext`.
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { convertLexicalToPlaintext } from '@payloadcms/richtext-lexical/plaintext'
// Your richtext data here
const data: SerializedEditorState = {}
const plaintext = convertLexicalToPlaintext({ data })
```
### Custom Converters
The `convertLexicalToPlaintext` functions accepts a `converters` object that allows you to customize how specific nodes are converted to plaintext.
```ts
import type {
DefaultNodeTypes,
SerializedBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import type { MyTextBlock } from '@/payload-types'
import {
convertLexicalToPlaintext,
type PlaintextConverters,
} from '@payloadcms/richtext-lexical/plaintext'
// Your richtext data here
const data: SerializedEditorState = {}
const converters: PlaintextConverters<
DefaultNodeTypes | SerializedBlockNode
> = {
blocks: {
textBlock: ({ node }) => {
return node.fields.text ?? ''
},
},
link: ({ node }) => {
return node.fields.url ?? ''
},
}
const plaintext = convertLexicalToPlaintext({
converters,
data,
})
```
Unlike other converters, there are no default converters for plaintext.
If a node does not have a converter defined, the following heuristics are used to convert it to plaintext:
- If the node has a `text` field, it will be used as the plaintext.
- If the node has a `children` field, the children will be recursively converted to plaintext.
- If the node has neither, it will be ignored.
- Paragraph, text and tab nodes insert newline / tab characters.
# Official Features
Source: https://payloadcms.com/docs/rich-text/official-features
Below are all the Rich Text Features Payload offers. Everything is customizable; you can [create your own features](../rich-text/custom-features), modify ours and share them with the community.
## Features Overview
| Feature Name | Included by default | Description |
| ------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`BoldFeature`** | Yes | Adds support for bold text formatting. |
| **`ItalicFeature`** | Yes | Adds support for italic text formatting. |
| **`UnderlineFeature`** | Yes | Adds support for underlined text formatting. |
| **`StrikethroughFeature`** | Yes | Adds support for strikethrough text formatting. |
| **`SubscriptFeature`** | Yes | Adds support for subscript text formatting. |
| **`SuperscriptFeature`** | Yes | Adds support for superscript text formatting. |
| **`InlineCodeFeature`** | Yes | Adds support for inline code formatting. |
| **`ParagraphFeature`** | Yes | Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion. |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Adds support for text alignment (left, center, right, justify) |
| **`IndentFeature`** | Yes | Adds support for text indentation with toolbar buttons |
| **`UnorderedListFeature`** | Yes | Adds support for unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds support for ordered lists (ol) |
| **`ChecklistFeature`** | Yes | Adds support for interactive 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 | Adds support for horizontal rules / separators. Basically displays an `` element |
| **`InlineToolbarFeature`** | Yes | Provides a floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text |
| **`FixedToolbarFeature`** | No | Provides a persistent toolbar pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time. |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](../fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Provides 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 |
| **`EXPERIMENTAL_TableFeature`** | No | Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release. |
| **`TextStateFeature`** | No | Allows you to store key-value attributes within TextNodes and assign them inline styles. |
## In depth
### BoldFeature
- Description: Adds support for bold text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `**bold**` or `__bold__`
- Keyboard Shortcut: Ctrl/Cmd + B
### ItalicFeature
- Description: Adds support for italic text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `*italic*` or `_italic_`
- Keyboard Shortcut: Ctrl/Cmd + I
### UnderlineFeature
- Description: Adds support for underlined text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Ctrl/Cmd + U
### StrikethroughFeature
- Description: Adds support for strikethrough text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `~~strikethrough~~`
### SubscriptFeature
- Description: Adds support for subscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
### SuperscriptFeature
- Description: Adds support for superscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
### InlineCodeFeature
- Description: Adds support for inline code formatting with distinct styling, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: \`code\`
### ParagraphFeature
- Description: Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion.
- Included by default: Yes
### HeadingFeature
- Description: Adds support for heading nodes (H1-H6) with toolbar dropdown and slash menu entries for each enabled heading size.
- Included by default: Yes
- Markdown Support: `#`, `##`, `###`, ..., at start of line.
- Types:
```ts
type HeadingFeatureProps = {
enabledHeadingSizes?: HeadingTagType[] // ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
}
```
- Usage example:
```ts
HeadingFeature({
enabledHeadingSizes: ['h1', 'h2', 'h3'], // Default: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
})
```
### AlignFeature
- Description: Allows text alignment (left, center, right, justify), along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Ctrl/Cmd + Shift + L/E/R/J (left/center/right/justify)
### IndentFeature
- Description: Adds support for text indentation, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Tab (increase), Shift + Tab (decrease)
- Types:
```ts
type IndentFeatureProps = {
/**
* The nodes that should not be indented. "type"
* property of the nodes you don't want to be indented.
* These can be: "paragraph", "heading", "listitem",
* "quote" or other indentable nodes if they exist.
*/
disabledNodes?: string[]
/**
* If true, pressing Tab in the middle of a block such
* as a paragraph or heading will not insert a tabNode.
* Instead, Tab will only be used for block-level indentation.
* @default false
*/
disableTabNode?: boolean
}
```
- Usage example:
```ts
// Allow block-level indentation only
IndentFeature({
disableTabNode: true,
})
```
### UnorderedListFeature
- Description: Adds support for unordered lists (bullet points) with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `-`, `*`, or `+` at start of line
### OrderedListFeature
- Description: Adds support for ordered lists (numbered lists) with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `1.` at start of line
### ChecklistFeature
- Description: Adds support for interactive checklists with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `- [ ]` (unchecked) or `- [x]` (checked)
### LinkFeature
- Description: Allows creation of internal and external links with toolbar buttons and automatic URL conversion.
- Included by default: Yes
- Markdown Support: `[anchor](url)`
- Types:
```ts
type LinkFeatureServerProps = {
/**
* Disables the automatic creation of links
* from URLs typed or pasted into the editor,
* @default false
*/
disableAutoLinks?: 'creationOnly' | true
/**
* A function or array defining additional
* fields for the link feature.
* These will be displayed in the link editor drawer.
*/
fields?:
| ((args: {
config: SanitizedConfig
defaultFields: FieldAffectingData[]
}) => (Field | FieldAffectingData)[])
| Field[]
/**
* Sets a maximum population depth for the internal
* doc default field of link, regardless of the
* remaining depth when the field is reached.
*/
maxDepth?: number
} & ExclusiveLinkCollectionsProps
type ExclusiveLinkCollectionsProps =
| {
disabledCollections?: CollectionSlug[]
enabledCollections?: never
}
| {
disabledCollections?: never
enabledCollections?: CollectionSlug[]
}
```
- Usage example:
```ts
LinkFeature({
fields: ({ defaultFields }) => [
...defaultFields,
{
name: 'rel',
type: 'select',
options: ['noopener', 'noreferrer', 'nofollow'],
},
],
enabledCollections: ['pages', 'posts'], // Collections for internal links
maxDepth: 2, // Population depth for internal links
disableAutoLinks: false, // Allow auto-conversion of URLs
})
```
### RelationshipFeature
- Description: Allows creation of block-level relationships to other documents with toolbar button and slash menu entry.
- Included by default: Yes
- Types:
```ts
type RelationshipFeatureProps = {
/**
* Sets a maximum population depth for this relationship,
* regardless of the remaining depth when the respective
* field is reached.
*/
maxDepth?: number
} & ExclusiveRelationshipFeatureProps
type ExclusiveRelationshipFeatureProps =
| {
disabledCollections?: CollectionSlug[]
enabledCollections?: never
}
| {
disabledCollections?: never
enabledCollections?: CollectionSlug[]
}
```
- Usage example:
```ts
RelationshipFeature({
disabledCollections: ['users'], // Collections to exclude
maxDepth: 2, // Population depth for relationships
})
```
### UploadFeature
- Description: Allows creation of upload/media nodes with toolbar button and slash menu entry, supports all file types.
- Included by default: Yes
- Types:
```ts
type UploadFeatureProps = {
collections?: {
[collection: UploadCollectionSlug]: {
fields: Field[]
}
}
/**
* Sets a maximum population depth for this upload (not the fields for this upload), regardless of the remaining depth when the respective field is reached.
* This behaves exactly like the maxDepth properties of relationship and upload fields.
*
* {@link ../getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
} & ExclusiveUploadFeatureProps
type ExclusiveUploadFeatureProps =
| {
/**
* The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
* When this property is set, `enabledCollections` will not be available.
**/
disabledCollections?: UploadCollectionSlug[]
// Ensures that enabledCollections is not available when disabledCollections is set
enabledCollections?: never
}
| {
// Ensures that disabledCollections is not available when enabledCollections is set
disabledCollections?: never
/**
* The collections that should be enabled. Overrides the `enableRichTextRelationship` property in the collection config
* When this property is set, `disabledCollections` will not be available.
**/
enabledCollections?: UploadCollectionSlug[]
}
```
- Usage example:
```ts
UploadFeature({
collections: {
uploads: {
fields: [
{
name: 'caption',
type: 'text',
label: 'Caption',
},
{
name: 'alt',
type: 'text',
label: 'Alt Text',
},
],
},
},
maxDepth: 1, // Population depth for uploads
disabledCollections: ['specialUploads'], // Collections to exclude
})
```
### BlockquoteFeature
- Description: Allows creation of blockquotes with toolbar button and slash menu entry.
- Included by default: Yes
- Markdown Support: `> quote text`
### HorizontalRuleFeature
- Description: Adds support for horizontal rules/separators with toolbar button and slash menu entry.
- Included by default: Yes
- Markdown Support: `---`
### InlineToolbarFeature
- Description: Provides a floating toolbar that appears when text is selected, containing formatting options relevant to selected text.
- Included by default: Yes
### FixedToolbarFeature
- Description: Provides a persistent toolbar pinned to the top of the editor that's always visible.
- Included by default: No
- Types:
```ts
type FixedToolbarFeatureProps = {
/**
* @default false
* If this is enabled, the toolbar will apply
* to the focused editor, not the editor with
* the FixedToolbarFeature.
*/
applyToFocusedEditor?: boolean
/**
* Custom configurations for toolbar groups
* Key is the group key (e.g. 'format', 'indent', 'align')
* Value is a partial ToolbarGroup object that will
* be merged with the default configuration
*/
customGroups?: CustomGroups
/**
* @default false
* If there is a parent editor with a fixed toolbar,
* this will disable the toolbar for this editor.
*/
disableIfParentHasFixedToolbar?: boolean
}
```
- Usage example:
```ts
FixedToolbarFeature({
applyToFocusedEditor: false, // Apply to focused editor
customGroups: {
format: {
// Custom configuration for format group
},
},
})
```
### BlocksFeature
- Description: Allows use of Payload's Blocks Field directly in the editor with toolbar buttons and slash menu entries for each block type.
- Included by default: No
- Types:
```ts
type BlocksFeatureProps = {
blocks?: (Block | BlockSlug)[] | Block[]
inlineBlocks?: (Block | BlockSlug)[] | Block[]
}
```
- Usage example:
```ts
BlocksFeature({
blocks: [
{
slug: 'callout',
fields: [
{
name: 'text',
type: 'text',
required: true,
},
],
},
],
inlineBlocks: [
{
slug: 'mention',
fields: [
{
name: 'name',
type: 'text',
required: true,
},
],
},
],
})
```
#### Code Blocks
Payload exports a premade CodeBlock that you can import and use in your project. It supports syntax highlighting, dynamically selecting the language and loading in external type definitions:
```ts
import { BlocksFeature, CodeBlock } from '@payloadcms/richtext-lexical'
// ...
BlocksFeature({
blocks: [
CodeBlock({
defaultLanguage: 'ts',
languages: {
js: 'JavaScript',
plaintext: 'Plain Text',
ts: 'TypeScript',
},
}),
],
}),
// ...
```
When using TypeScript, you can also pass in additional type definitions that will be available in the editor. Here's an example of how to make `payload` and `react` available in the editor:
```ts
import { BlocksFeature, CodeBlock } from '@payloadcms/richtext-lexical'
// ...
BlocksFeature({
blocks: [
CodeBlock({
slug: 'PayloadCode',
languages: {
ts: 'TypeScript',
},
typescript: {
fetchTypes: [
{
// The index.bundled.d.ts contains all the types for Payload in one file, so that Monaco doesn't need to fetch multiple files.
// This file may be removed in the future and is not guaranteed to be available in future versions of Payload.
url: 'https://unpkg.com/payload@3.59.0-internal.8435f3c/dist/index.bundled.d.ts',
filePath: 'file:///node_modules/payload/index.d.ts',
},
{
url: 'https://unpkg.com/@types/react@19.1.17/index.d.ts',
filePath: 'file:///node_modules/@types/react/index.d.ts',
},
],
paths: {
payload: ['file:///node_modules/payload/index.d.ts'],
react: ['file:///node_modules/@types/react/index.d.ts'],
},
typeRoots: ['node_modules/@types', 'node_modules/payload'],
// Enable type checking. By default, only syntax checking is enabled.
enableSemanticValidation: true,
},
}),
],
}),
// ...
```
### TreeViewFeature
- Description: Provides a debug panel below the editor showing the editor's internal state, DOM tree, and time travel debugging.
- Included by default: No
### EXPERIMENTAL_TableFeature
- Description: Adds support for tables with toolbar button and slash menu entry for creation and editing.
- Included by default: No
### TextStateFeature
- Description: Allows storing key-value attributes in text nodes with inline styles and toolbar dropdown for style selection.
- Included by default: No
- Types:
```ts
type TextStateFeatureProps = {
/**
* The keys of the top-level object (stateKeys) represent the attributes that the textNode can have (e.g., color).
* The values of the top-level object (stateValues) represent the values that the attribute can have (e.g., red, blue, etc.).
* Within the stateValue, you can define inline styles and labels.
*/
state: { [stateKey: string]: StateValues }
}
type StateValues = {
[stateValue: string]: {
css: StyleObject
label: string
}
}
type StyleObject = {
[K in keyof PropertiesHyphenFallback]?:
| Extract
| undefined
}
```
- Usage example:
```ts
// We offer default colors that have good contrast and look good in dark and light mode.
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'
TextStateFeature({
// prettier-ignore
state: {
color: {
...defaultColors,
// fancy gradients!
galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
},
// You can have both colored and underlined text at the same time.
// If you don't want that, you should group them within the same key.
// (just like I did with defaultColors and my fancy gradients)
underline: {
'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
// You'll probably want to use the CSS light-dark() utility.
'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
},
},
}),
```
This is what the example above will look like:
# Custom Features
Source: https://payloadcms.com/docs/rich-text/custom-features
Before you begin building custom features for Lexical, it is crucial to familiarize yourself with the [Lexical docs](https://lexical.dev/docs/intro), particularly the "Concepts" section. This foundation is necessary for understanding Lexical's core principles, such as nodes, editor state, and commands.
Lexical features are designed to be modular, meaning each piece of functionality is encapsulated within just two specific interfaces: one for server-side code and one for client-side code.
By convention, these are named `feature.server.ts` for server-side functionality and `feature.client.ts` for client-side functionality. The primary functionality is housed within `feature.server.ts`, which users will import into their projects. The client-side feature, although defined separately, is integrated and rendered server-side through the server feature.
That way, we still maintain a clear boundary between server and client code, while also centralizing the code needed for a feature in basically one place. This approach is beneficial for managing all the bits and pieces which make up your feature as a whole, such as toolbar entries, buttons, or new nodes, allowing each feature to be neatly contained and managed independently.
**Important:**
Do not import directly from core lexical packages - this may break in minor Payload version bumps.
Instead, import the re-exported versions from `@payloadcms/richtext-lexical`. For example, change `import { $insertNodeToNearestRoot } from '@lexical/utils'` to `import { $insertNodeToNearestRoot } from '@payloadcms/richtext-lexical/lexical/utils'`
## Do I need a custom feature?
Before you start building a custom feature, consider whether you can achieve your desired functionality using the existing `BlocksFeature`. The `BlocksFeature` is a powerful feature that allows you to create custom blocks with a variety of options, including custom React components, markdown converters, and more. If you can achieve your desired functionality using the `BlocksFeature`, it is recommended to use it instead of building a custom feature.
Using the BlocksFeature, you can add both inline blocks (= can be inserted into a paragraph, in between text) and block blocks (= take up the whole line) to the editor. If you simply want to bring custom react components into the editor, this is the way to go.
### Example: Code Field Block with language picker
This example demonstrates how to create a custom code field block with a language picker using the `BlocksFeature`. First, make sure to explicitly install `@payloadcms/ui` in your project.
Field Config:
```ts
import {
BlocksFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
export const languages = {
ts: 'TypeScript',
plaintext: 'Plain Text',
tsx: 'TSX',
js: 'JavaScript',
jsx: 'JSX',
}
// ...
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [
{
slug: 'Code',
fields: [
{
type: 'select',
name: 'language',
options: Object.entries(languages).map(([key, value]) => ({
label: value,
value: key,
})),
defaultValue: 'ts',
},
{
admin: {
components: {
Field: './path/to/CodeComponent#Code',
},
},
name: 'code',
type: 'code',
},
],
}
],
inlineBlocks: [],
}),
],
}),
},
```
CodeComponent.tsx:
```tsx
'use client'
import type { CodeFieldClient, CodeFieldClientProps } from 'payload'
import { CodeField, useFormFields } from '@payloadcms/ui'
import React, { useMemo } from 'react'
import { languages } from './yourFieldConfig'
const languageKeyToMonacoLanguageMap = {
plaintext: 'plaintext',
ts: 'typescript',
tsx: 'typescript',
}
type Language = keyof typeof languageKeyToMonacoLanguageMap
export const Code: React.FC = ({
autoComplete,
field,
forceRender,
path,
permissions,
readOnly,
renderedBlocks,
schemaPath,
validate,
}) => {
const languageField = useFormFields(([fields]) => fields['language'])
const language: Language =
(languageField?.value as Language) ||
(languageField?.initialValue as Language) ||
'ts'
const label = languages[language]
const props: CodeFieldClient = useMemo(
() => ({
...field,
type: 'code',
admin: {
...field.admin,
editorOptions: undefined,
language: languageKeyToMonacoLanguageMap[language] || language,
},
label,
}),
[field, language, label],
)
const key = `${field.name}-${language}-${label}`
return (
)
}
```
## Server Feature
Custom Blocks are not enough? To start building a custom feature, you should start with the server feature, which is the entry-point.
**Example myFeature/feature.server.ts:**
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
export const MyFeature = createServerFeature({
feature: {},
key: 'myFeature',
})
```
`createServerFeature` is a helper function which lets you create new features without boilerplate code.
Now, the feature is ready to be used in the editor:
```ts
import { MyFeature } from './myFeature/feature.server';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
//...
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
features: [
MyFeature(),
],
}),
},
```
By default, this server feature does nothing - you haven't added any functionality yet. Depending on what you want your feature to do, the ServerFeature type exposes various properties you can set to inject custom functionality into the lexical editor.
### i18n
Each feature can register their own translations, which are automatically scoped to the feature key:
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
export const MyFeature = createServerFeature({
feature: {
i18n: {
en: {
label: 'My Feature',
},
de: {
label: 'Mein Feature',
},
},
},
key: 'myFeature',
})
```
This allows you to add i18n translations scoped to your feature. This specific example translation will be available under `lexical:myFeature:label` - `myFeature` being your feature key.
### Markdown Transformers#server-feature-markdown-transformers
The Server Feature, just like the Client Feature, allows you to add markdown transformers. Markdown transformers on the server are used when [converting the editor from or to markdown](../rich-text/converting-markdown).
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
import type { ElementTransformer } from '@payloadcms/richtext-lexical/lexical/markdown'
import { $createMyNode, $isMyNode, MyNode } from './nodes/MyNode'
const MyMarkdownTransformer: ElementTransformer = {
type: 'element',
dependencies: [MyNode],
export: (node, exportChildren) => {
if (!$isMyNode(node)) {
return null
}
return '+++'
},
// match ---
regExp: /^+++\s*$/,
replace: (parentNode) => {
const node = $createMyNode()
if (node) {
parentNode.replace(node)
}
},
}
export const MyFeature = createServerFeature({
feature: {
markdownTransformers: [MyMarkdownTransformer],
},
key: 'myFeature',
})
```
In this example, the node will be outputted as `+++` in Markdown, and the markdown `+++` will be converted to a `MyNode` node in the editor.
### Nodes#server-feature-nodes
While nodes added to the server feature do not control how the node is rendered in the editor, they control other aspects of the node:
- HTML conversion
- Node Hooks
- Sub fields
- Behavior in a headless editor
The `createNode` helper function is used to create nodes with proper typing. It is recommended to use this function to create nodes.
```ts
import { createServerFeature, createNode } from '@payloadcms/richtext-lexical'
import { MyNode } from './nodes/MyNode'
export const MyFeature = createServerFeature({
feature: {
nodes: [
// Use the createNode helper function to more easily create nodes with proper typing
createNode({
converters: {
html: {
converter: () => {
return ``
},
nodeTypes: [MyNode.getType()],
},
},
// Here you can add your actual node. On the server, they will be
// used to initialize a headless editor which can be used to perform
// operations on the editor, like markdown / html conversion.
node: MyNode,
}),
],
},
key: 'myFeature',
})
```
While nodes in the client feature are added by themselves to the nodes array, nodes in the server feature can be added together with the following sibling options:
| Option | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`getSubFields`** | If a node includes sub-fields (e.g. block and link nodes), passing the subFields schema here will make Payload automatically populate & run hooks for them. |
| **`getSubFieldsData`** | If a node includes sub-fields, the sub-fields data needs to be returned here, alongside `getSubFields` which returns their schema. |
| **`graphQLPopulationPromises`** | Allows you to run population logic when a node's data was requested from GraphQL. While `getSubFields` and `getSubFieldsData` automatically handle populating sub-fields (since they run hooks on them), those are only populated in the Rest API. This is because the Rest API hooks do not have access to the 'depth' property provided by GraphQL. In order for them to be populated correctly in GraphQL, the population logic needs to be provided here. |
| **`node`** | The actual lexical node needs to be provided here. This also supports [lexical node replacements](https://lexical.dev/docs/concepts/node-replacement). |
| **`validations`** | This allows you to provide node validations, which are run when your document is being validated, alongside other Payload fields. You can use it to throw a validation error for a specific node in case its data is incorrect. |
| **`converters`** | Allows you to define how a node can be serialized into different formats. Currently, only supports HTML. Markdown converters are defined in `markdownTransformers` and not here. |
| **`hooks`** | Just like Payload fields, you can provide hooks which are run for this specific node. These are called Node Hooks. |
### Feature load order
Server features can also accept a function as the `feature` property (useful for sanitizing props, as mentioned below). This function will be called when the feature is loaded during the Payload sanitization process:
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
createServerFeature({
//...
feature: async ({
config,
isRoot,
props,
resolvedFeatures,
unSanitizedEditorConfig,
featureProviderMap,
}) => {
return {
//Actual server feature here...
}
},
})
```
"Loading" here means the process of calling this `feature` function. By default, features are called in the order in which they are added to the editor.
However, sometimes you might want to load a feature after another feature has been loaded, or require a different feature to be loaded, throwing an error if this is not the case.
Within lexical, one example where this is done are our list features. Both `UnorderedListFeature` and `OrderedListFeature` register the same `ListItem` node. Within `UnorderedListFeature` we register it normally, but within `OrderedListFeature` we want to only register the `ListItem` node if the `UnorderedListFeature` is not present - otherwise, we would have two features registering the same node.
Here is how we do it:
```ts
import { createServerFeature, createNode } from '@payloadcms/richtext-lexical'
export const OrderedListFeature = createServerFeature({
feature: ({ featureProviderMap }) => {
return {
// ...
nodes: featureProviderMap.has('unorderedList')
? []
: [
createNode({
// ...
}),
],
}
},
key: 'orderedList',
})
```
`featureProviderMap` will always be available and contain all the features, even yet-to-be-loaded ones, so we can check if a feature is loaded by checking if its `key` present in the map.
If you wanted to make sure a feature is loaded before another feature, you can use the `dependenciesPriority` property:
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
export const MyFeature = createServerFeature({
feature: ({ featureProviderMap }) => {
return {
// ...
}
},
key: 'myFeature',
dependenciesPriority: ['otherFeature'],
})
```
| Option | Description |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`dependenciesSoft`** | Keys of soft-dependencies needed for this feature. These are optional. Payload will attempt to load them before this feature, but doesn't throw an error if that's not possible. |
| **`dependencies`** | Keys of dependencies needed for this feature. These dependencies do not have to be loaded first, but they have to exist, otherwise an error will be thrown. |
| **`dependenciesPriority`** | Keys of priority dependencies needed for this feature. These dependencies have to be loaded first AND have to exist, otherwise an error will be thrown. They will be available in the `feature` property. |
## Client Feature
Most of the functionality which the user actually sees and interacts with, like toolbar items and React components for nodes, resides on the client-side.
To set up your client-side feature, follow these three steps:
1. **Create a Separate File**: Start by creating a new file specifically for your client feature, such as `myFeature/feature.client.ts`. It's important to keep client and server features in separate files to maintain a clean boundary between server and client code.
2. **'use client'**: Mark that file with a 'use client' directive at the top of the file
3. **Register the Client Feature**: Register the client feature within your server feature, by passing it to the `ClientFeature` prop. This is needed because the server feature is the sole entry-point of your feature. This also means you are not able to create a client feature without a server feature, as you will not be able to register it otherwise.
**Example myFeature/feature.client.ts:**
```ts
'use client'
import { createClientFeature } from '@payloadcms/richtext-lexical/client'
export const MyClientFeature = createClientFeature({})
```
Explore the APIs available through ClientFeature to add the specific functionality you need. Remember, do not import directly from `'@payloadcms/richtext-lexical'` when working on the client-side, as it will cause errors with webpack or turbopack. Instead, use `'@payloadcms/richtext-lexical/client'` for all client-side imports. Type-imports are excluded from this rule and can always be imported.
### Adding a client feature to the server feature
Inside of your server feature, you can provide an [import path](../custom-components/overview#component-paths) to the client feature like this:
```ts
import { createServerFeature } from '@payloadcms/richtext-lexical'
export const MyFeature = createServerFeature({
feature: {
ClientFeature: './path/to/feature.client#MyClientFeature',
},
key: 'myFeature',
dependenciesPriority: ['otherFeature'],
})
```
### Nodes#client-feature-nodes
Add nodes to the `nodes` array in **both** your client & server feature. On the server side, nodes are utilized for backend operations like HTML conversion in a headless editor. On the client side, these nodes are integral to how content is displayed and managed in the editor, influencing how they are rendered, behave, and saved in the database.
Example:
**myFeature/feature.client.ts:**
```ts
'use client'
import { createClientFeature } from '@payloadcms/richtext-lexical/client'
import { MyNode } from './nodes/MyNode'
export const MyClientFeature = createClientFeature({
nodes: [MyNode],
})
```
This also supports [lexical node replacements](https://lexical.dev/docs/concepts/node-replacement).
**myFeature/nodes/MyNode.tsx:**
Here is a basic DecoratorNode example:
```ts
import type {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
LexicalNode,
SerializedLexicalNode,
} from '@payloadcms/richtext-lexical/lexical'
import { $applyNodeReplacement, DecoratorNode } from '@payloadcms/richtext-lexical/lexical'
// SerializedLexicalNode is the default lexical node.
// By setting your SerializedMyNode type to SerializedLexicalNode,
// you are basically saying that this node does not save any additional data.
// If you want your node to save data, feel free to extend it
export type SerializedMyNode = SerializedLexicalNode
// Lazy-import the React component to your node here
const MyNodeComponent = React.lazy(() =>
import('../component/index.js').then((module) => ({
default: module.MyNodeComponent,
})),
)
/**
* This node is a DecoratorNode. DecoratorNodes allow
* you to render React components in the editor.
*
* They need both createDom and decorate functions.
* createDom => outside of the html.
* decorate => React Component inside of the html.
*
* If we used DecoratorBlockNode instead,
* we would only need a decorate method
*/
export class MyNode extends DecoratorNode {
static clone(node: MyNode): MyNode {
return new MyNode(node.__key)
}
static getType(): string {
return 'myNode'
}
/**
* Defines what happens if you copy a div element
* from another page and paste it into the lexical editor
*
* This also determines the behavior of lexical's
* internal HTML -> Lexical converter
*/
static importDOM(): DOMConversionMap | null {
return {
div: () => ({
conversion: $yourConversionMethod,
priority: 0,
}),
}
}
/**
* The data for this node is stored serialized as JSON.
* This is the "load function" of that node: it takes
* the saved data and converts it into a node.
*/
static importJSON(serializedNode: SerializedMyNode): MyNode {
return $createMyNode()
}
/**
* Determines how the hr element is rendered in the
* lexical editor. This is only the "initial" / "outer"
* HTML element.
*/
createDOM(config: EditorConfig): HTMLElement {
const element = document.createElement('div')
return element
}
/**
* Allows you to render a React component within
* whatever createDOM returns.
*/
decorate(): React.ReactElement {
return
}
/**
* Opposite of importDOM, this function defines what
* happens when you copy a div element from the lexical
* editor and paste it into another page.
*
* This also determines the behavior of lexical's
* internal Lexical -> HTML converter
*/
exportDOM(): DOMExportOutput {
return { element: document.createElement('div') }
}
/**
* Opposite of importJSON. This determines what
* data is saved in the database / in the lexical
* editor state.
*/
exportJSON(): SerializedLexicalNode {
return {
type: 'myNode',
version: 1,
}
}
getTextContent(): string {
return '\n'
}
isInline(): false {
return false
}
updateDOM(): boolean {
return false
}
}
// This is used in the importDOM method. Totally optional
// if you do not want your node to be created automatically
// when copy & pasting certain dom elements into your editor.
function $yourConversionMethod(): DOMConversionOutput {
return { node: $createMyNode() }
}
// This is a utility method to create a new MyNode.
// Utility methods prefixed with $ make it explicit
// that this should only be used within lexical
export function $createMyNode(): MyNode {
return $applyNodeReplacement(new MyNode())
}
// This is just a utility method you can use
// to check if a node is a MyNode. This also
// ensures correct typing.
export function $isMyNode(
node: LexicalNode | null | undefined,
): node is MyNode {
return node instanceof MyNode
}
```
Please do not add any 'use client' directives to your nodes, as the node class can be used on the server.
### Plugins
One small part of a feature are plugins. The name stems from the lexical playground plugins and is just a small part of a lexical feature.
Plugins are simply React components which are added to the editor, within all the lexical context providers. They can be used to add any functionality
to the editor, by utilizing the lexical API.
Most commonly, they are used to register [lexical listeners](https://lexical.dev/docs/concepts/listeners), [node transforms](https://lexical.dev/docs/concepts/transforms) or [commands](https://lexical.dev/docs/concepts/commands).
For example, you could add a drawer to your plugin and register a command which opens it. That command can then be called from anywhere within lexical, e.g. from within your custom lexical node.
To add a plugin, simply add it to the `plugins` array in your client feature:
```ts
'use client'
import { createClientFeature } from '@payloadcms/richtext-lexical/client'
import { MyPlugin } from './plugin'
export const MyClientFeature = createClientFeature({
plugins: [MyPlugin],
})
```
Example plugin.tsx:
```ts
'use client'
import type { LexicalCommand } from '@payloadcms/richtext-lexical/lexical'
import {
createCommand,
$getSelection,
$isRangeSelection,
COMMAND_PRIORITY_EDITOR,
} from '@payloadcms/richtext-lexical/lexical'
import { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'
import { $insertNodeToNearestRoot } from '@payloadcms/richtext-lexical/lexical/utils'
import { useEffect } from 'react'
import type { PluginComponent } from '@payloadcms/richtext-lexical' // type imports can be imported from @payloadcms/richtext-lexical - even on the client
import { $createMyNode } from '../nodes/MyNode'
import './index.scss'
export const INSERT_MYNODE_COMMAND: LexicalCommand = createCommand(
'INSERT_MYNODE_COMMAND',
)
/**
* Plugin which registers a lexical command to
* insert a new MyNode into the editor
*/
export const MyNodePlugin: PluginComponent = () => {
// The useLexicalComposerContext hook can be used
// to access the lexical editor instance
const [editor] = useLexicalComposerContext()
useEffect(() => {
return editor.registerCommand(
INSERT_MYNODE_COMMAND,
(type) => {
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
return false
}
const focusNode = selection.focus.getNode()
if (focusNode !== null) {
const newMyNode = $createMyNode()
$insertNodeToNearestRoot(newMyNode)
}
return true
},
COMMAND_PRIORITY_EDITOR,
)
}, [editor])
return null
}
```
In this example, we register a lexical command, which simply inserts a new MyNode into the editor. This command can be called from anywhere within lexical, e.g. from within a custom node.
### Toolbar groups
Toolbar groups are visual containers which hold toolbar items. There are different toolbar group types which determine _how_ a toolbar item is displayed: `dropdown` and `buttons`.
All the default toolbar groups are exported from `@payloadcms/richtext-lexical/client`. You can use them to add your own toolbar items to the editor:
- Dropdown: `toolbarAddDropdownGroupWithItems`
- Dropdown: `toolbarTextDropdownGroupWithItems`
- Buttons: `toolbarFormatGroupWithItems`
- Buttons: `toolbarFeatureButtonsGroupWithItems`
Within dropdown groups, items are positioned vertically when the dropdown is opened and include the icon & label. Within button groups, items are positioned horizontally and only include the icon. If a toolbar group with the same key is declared twice, all its items will be merged into one group.
#### Custom buttons toolbar group
| Option | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`items`** | All toolbar items part of this toolbar group need to be added here. |
| **`key`** | Each toolbar group needs to have a unique key. Groups with the same keys will have their items merged together. |
| **`order`** | Determines where the toolbar group will be. |
| **`type`** | Controls the toolbar group type. Set to `buttons` to create a buttons toolbar group, which displays toolbar items horizontally using only their icons. |
Example:
```ts
import type {
ToolbarGroup,
ToolbarGroupItem,
} from '@payloadcms/richtext-lexical'
export const toolbarFormatGroupWithItems = (
items: ToolbarGroupItem[],
): ToolbarGroup => {
return {
type: 'buttons',
items,
key: 'myButtonsToolbar',
order: 10,
}
}
```
#### Custom dropdown toolbar group
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`items`** | All toolbar items part of this toolbar group need to be added here. |
| **`key`** | Each toolbar group needs to have a unique key. Groups with the same keys will have their items merged together. |
| **`order`** | Determines where the toolbar group will be. |
| **`type`** | Controls the toolbar group type. Set to `dropdown` to create a buttons toolbar group, which displays toolbar items vertically using their icons and labels, if the dropdown is open. |
| **`ChildComponent`** | The dropdown toolbar ChildComponent allows you to pass in a React Component which will be displayed within the dropdown button. |
Example:
```ts
import type {
ToolbarGroup,
ToolbarGroupItem,
} from '@payloadcms/richtext-lexical'
import { MyIcon } from './icons/MyIcon'
export const toolbarAddDropdownGroupWithItems = (
items: ToolbarGroupItem[],
): ToolbarGroup => {
return {
type: 'dropdown',
ChildComponent: MyIcon,
items,
key: 'myDropdownToolbar',
order: 10,
}
}
```
### Toolbar items
Custom nodes and features on its own are pointless, if they can't be added to the editor. You will need to hook in one of our interfaces which allow the user to interact with the editor:
- Fixed toolbar which stays fixed at the top of the editor
- Inline, floating toolbar which appears when selecting text
- Slash menu which appears when typing `/` in the editor
- Markdown transformers, which are triggered when a certain text pattern is typed in the editor
- Or any other interfaces which can be added via your own plugins. Our toolbars are a prime example of this - they are just plugins.
To add a toolbar item to either the floating or the inline toolbar, you can add a ToolbarGroup with a ToolbarItem to the `toolbarFixed` or `toolbarInline` props of your client feature:
```ts
'use client'
import {
createClientFeature,
toolbarAddDropdownGroupWithItems,
} from '@payloadcms/richtext-lexical/client'
import { IconComponent } from './icon'
import { $isHorizontalRuleNode } from './nodes/MyNode'
import { INSERT_MYNODE_COMMAND } from './plugin'
import { $isNodeSelection } from '@payloadcms/richtext-lexical/lexical'
export const MyClientFeature = createClientFeature({
toolbarFixed: {
groups: [
toolbarAddDropdownGroupWithItems([
{
ChildComponent: IconComponent,
isActive: ({ selection }) => {
if (!$isNodeSelection(selection) || !selection.getNodes().length) {
return false
}
const firstNode = selection.getNodes()[0]
return $isHorizontalRuleNode(firstNode)
},
key: 'myNode',
label: ({ i18n }) => {
return i18n.t('lexical:myFeature:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_MYNODE_COMMAND, undefined)
},
},
]),
],
},
})
```
You will have to provide a toolbar group first, and then the items for that toolbar group (more on that above).
A `ToolbarItem` various props you can use to customize its behavior:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`ChildComponent`** | A React component which is rendered within your toolbar item's default button component. Usually, you want this to be an icon. |
| **`Component`** | A React component which is rendered in place of the toolbar item's default button component, thus completely replacing it. The `ChildComponent` and `onSelect` properties will be ignored. |
| **`label`** | The label will be displayed in your toolbar item, if it's within a dropdown group. To make use of i18n, this can be a function. |
| **`key`** | Each toolbar item needs to have a unique key. |
| **`onSelect`** | A function which is called when the toolbar item is clicked. |
| **`isEnabled`** | This is optional and controls if the toolbar item is clickable or not. If `false` is returned here, it will be grayed out and unclickable. |
| **`isActive`** | This is optional and controls if the toolbar item is highlighted or not |
The API for adding an item to the floating inline toolbar (`toolbarInline`) is identical. If you wanted to add an item to both the fixed and inline toolbar, you can extract it into its own variable
(typed as `ToolbarGroup[]`) and add it to both the `toolbarFixed` and `toolbarInline` props.
### Slash Menu groups
We're exporting `slashMenuBasicGroupWithItems` from `@payloadcms/richtext-lexical/client` which you can use to add items to the slash menu labelled "Basic". If you want to create your own slash menu group, here is an example:
```ts
import type {
SlashMenuGroup,
SlashMenuItem,
} from '@payloadcms/richtext-lexical'
export function mwnSlashMenuGroupWithItems(
items: SlashMenuItem[],
): SlashMenuGroup {
return {
items,
key: 'myGroup',
label: 'My Group', // <= This can be a function to make use of i18n
}
}
```
By creating a helper function like this, you can easily re-use it and add items to it. All Slash Menu groups with the same keys will have their items merged together.
| Option | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **`items`** | An array of `SlashMenuItem`'s which will be displayed in the slash menu. |
| **`label`** | The label will be displayed before your Slash Menu group. In order to make use of i18n, this can be a function. |
| **`key`** | Used for class names and, if label is not provided, for display. Slash menus with the same key will have their items merged together. |
### Slash Menu items
The API for adding items to the slash menu is similar. There are slash menu groups, and each slash menu groups has items. Here is an example:
```ts
'use client'
import {
createClientFeature,
slashMenuBasicGroupWithItems,
} from '@payloadcms/richtext-lexical/client'
import { INSERT_MYNODE_COMMAND } from './plugin'
import { IconComponent } from './icon'
export const MyClientFeature = createClientFeature({
slashMenu: {
groups: [
slashMenuBasicGroupWithItems([
{
Icon: IconComponent,
key: 'myNode',
keywords: ['myNode', 'myFeature', 'someOtherKeyword'],
label: ({ i18n }) => {
return i18n.t('lexical:myFeature:label')
},
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_MYNODE_COMMAND, undefined)
},
},
]),
],
},
})
```
| Option | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Icon`** | The icon which is rendered in your slash menu item. |
| **`label`** | The label will be displayed in your slash menu item. In order to make use of i18n, this can be a function. |
| **`key`** | Each slash menu item needs to have a unique key. The key will be matched when typing, displayed if no `label` property is set, and used for classNames. |
| **`onSelect`** | A function which is called when the slash menu item is selected. |
| **`keywords`** | Keywords are used to match the item for different texts typed after the '/'. E.g. you might want to show a horizontal rule item if you type both /hr, /separator, /horizontal etc. In addition to the keywords, the label and key will be used to find the right slash menu item. |
### Markdown Transformers#client-feature-markdown-transformers
The Client Feature, just like the Server Feature, allows you to add markdown transformers. Markdown transformers on the client are used to create new nodes when a certain markdown pattern is typed in the editor.
```ts
import { createClientFeature } from '@payloadcms/richtext-lexical/client'
import type { ElementTransformer } from '@payloadcms/richtext-lexical/lexical/markdown'
import { $createMyNode, $isMyNode, MyNode } from './nodes/MyNode'
const MyMarkdownTransformer: ElementTransformer = {
type: 'element',
dependencies: [MyNode],
export: (node, exportChildren) => {
if (!$isMyNode(node)) {
return null
}
return '+++'
},
// match ---
regExp: /^+++\s*$/,
replace: (parentNode) => {
const node = $createMyNode()
if (node) {
parentNode.replace(node)
}
},
}
export const MyFeature = createClientFeature({
markdownTransformers: [MyMarkdownTransformer],
})
```
In this example, a new `MyNode` will be inserted into the editor when `+++ ` is typed.
### Providers
You can add providers to your client feature, which will be nested below the `EditorConfigProvider`. This can be useful if you want to provide some context to your nodes or other parts of your feature.
```ts
'use client'
import { createClientFeature } from '@payloadcms/richtext-lexical/client'
import { TableContext } from './context'
export const MyClientFeature = createClientFeature({
providers: [TableContext],
})
```
## Props
To accept props in your feature, type them as a generic.
Server Feature:
```ts
createServerFeature({
//...
})
```
Client Feature:
```ts
createClientFeature({
//...
})
```
The unSanitized props are what the user will pass to the feature when they call its provider function and add it to their editor config. You then have an option to sanitize those props.
To sanitize those in the server feature, you can pass a function to `feature` instead of an object:
```ts
createServerFeature({
//...
feature: async ({
config,
isRoot,
props,
resolvedFeatures,
unSanitizedEditorConfig,
featureProviderMap,
}) => {
const sanitizedProps = doSomethingWithProps(props)
return {
sanitizedServerFeatureProps: sanitizedProps,
//Actual server feature here...
}
},
})
```
Keep in mind that any sanitized props then have to be returned in the `sanitizedServerFeatureProps` property.
In the client feature, it works similarly:
```ts
createClientFeature(
({
clientFunctions,
featureProviderMap,
props,
resolvedFeatures,
unSanitizedEditorConfig,
}) => {
const sanitizedProps = doSomethingWithProps(props)
return {
sanitizedClientFeatureProps: sanitizedProps,
//Actual client feature here...
}
},
)
```
### Bringing props from the server to the client
By default, the client feature will never receive any props from the server feature. In order to pass props from the server to the client, you can need to return those props in the server feature:
```ts
type UnSanitizedClientProps = {
test: string
}
createServerFeature({
//...
feature: {
clientFeatureProps: {
test: 'myValue',
},
},
})
```
The reason the client feature does not have the same props available as the server by default is because all client props need to be serializable. You can totally accept things like functions or Maps as props in your server feature, but you will not be able to send those to the client. In the end, those props are sent from the server to the client over the network, so they need to be serializable.
## More information
Have a look at the [features we've already built](https://github.com/payloadcms/payload/tree/main/packages/richtext-lexical/src/features) - understanding how they work will help you understand how to create your own. There is no difference between the features included by default and the ones you create yourself - since those features are all isolated from the "core", you have access to the same APIs, whether the feature is part of Payload or not!
# Rendering On Demand
Source: https://payloadcms.com/docs/rich-text/rendering-on-demand
Lexical in Payload is a **React Server Component (RSC)**. Historically that created three headaches: 1. You couldn't render the editor directly from the client. 2. Features like blocks, tables and link drawers require the server to know the shape of nested sub-fields at render time. If you tried to render on demand, the server didn't know those schemas. 3. The rich text field is designed to live inside a `Form`. For simple use cases, setting up a full form just to manage editor state was cumbersome.
To simplify rendering richtext on demand, , that renders a Lexical editor while still covering the full feature set. On mount, it calls a server action to render the editor on the server using the new `render-field` server function. That server render gives Lexical everything it needs (including nested field schemas) and returns a ready-to-hydrate editor.
`RenderLexical` and the underlying `render-field` server function are
experimental and may change in minor releases.
## Inside an existing Form
If you have an existing Form and want to render a richtext field within it, you can use the `RenderLexical` component like this:
```tsx
'use client'
import type { JSONFieldClientComponent } from 'payload'
import {
buildEditorState,
RenderLexical,
} from '@payloadcms/richtext-lexical/client'
import { lexicalFullyFeaturedSlug } from '../../slugs.js'
export const Component: JSONFieldClientComponent = (args) => {
return (
({
text: 'default value',
})}
schemaPath={`collection.${lexicalFullyFeaturedSlug}.richText`}
/>
)
}
```
## Outside of a Form (you control state)
```tsx
'use client'
import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
import type { JSONFieldClientComponent } from 'payload'
import {
buildEditorState,
RenderLexical,
} from '@payloadcms/richtext-lexical/client'
import React, { useState } from 'react'
import { lexicalFullyFeaturedSlug } from '../../slugs.js'
export const Component: JSONFieldClientComponent = (args) => {
// Manually manage the editor state
const [value, setValue] = useState(() =>
buildEditorState({ text: 'state default' }),
)
const handleReset = React.useCallback(() => {
setValue(buildEditorState({ text: 'state default' }))
}, [])
return (
)
}
```
## Choosing the schemaPath
`schemaPath` tells the server which richText field to render. This gives the server the exact nested field schemas (blocks, relationship drawers, upload fields, tables, etc.).
Format:
- `collection..`
- `global..`
Example (top level): `collection.posts.richText`
Example (nested in a group/tab): `collection.posts.content.richText`
**Tip:** If your target editor lives deep in arrays/blocks and you're unsure of the exact path, you can define a **hidden top-level richText** purely as a "render anchor":
```ts
{
name: 'onDemandAnchor',
type: 'richText',
admin: { hidden: true }
}
```
Then use `schemaPath="collection.posts.onDemandAnchor"`
# Lexical Migration
Source: https://payloadcms.com/docs/rich-text/migration
## Migrating from Slate
While both Slate and Lexical save the editor state in JSON, the structure of the JSON is different.
### Migration via Migration Script (Recommended)
Just import the `migrateSlateToLexical` function we provide, pass it the `payload` object and run it. Depending on the amount of collections, this might take a while.
IMPORTANT: This will overwrite all slate data. We recommend doing the following first:
1. Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.
2. Make every richText field a lexical editor. This script will only convert lexical richText fields with old Slate data.
3. Add the SlateToLexicalFeature (as seen below) first, and test it out by loading up the Admin Panel, to see if the migrator works as expected. You might have to build some custom converters for some fields first in order to convert custom Slate nodes. The SlateToLexicalFeature is where the converters are stored. Only fields with this feature added will be migrated.
4. If this works as expected, add the `disableHooks: true` prop everywhere you're initializing `SlateToLexicalFeature`. Example: `SlateToLexicalFeature({ disableHooks: true })`. Once you did that, you're ready to run the migration script.
```ts
import { migrateSlateToLexical } from '@payloadcms/richtext-lexical/migrate'
await migrateSlateToLexical({ payload })
```
### Migration via SlateToLexicalFeature
One way to handle this is to just give your lexical editor the ability to read the slate JSON.
Simply add the `SlateToLexicalFeature` to your editor:
```ts
import type { CollectionConfig } from 'payload'
import { SlateToLexicalFeature } from '@payloadcms/richtext-lexical/migrate'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
SlateToLexicalFeature({}),
],
}),
},
],
}
```
and done! Now, every time 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: Edit the richText field and save the document! This overrides the slate data with the lexical data, and the next time the document is loaded, the lexical data will be used. This solves both the performance and the output issue for that specific document. This, however, is a slow and gradual migration process, thus you will have to support both API formats. Especially for a large number of documents, we recommend running the migration script, as explained above.
### 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'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import {
SlateToLexicalFeature,
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`.
## Migrating lexical data from old version to new version
Each lexical node has a `version` property which is saved in the database. Every time we make a breaking change to the node's data, we increment the version. This way, we can detect an old version and automatically convert old data to the new format once you open up the editor.
The problem is, this migration only happens when you open the editor, modify the richText field (so that the field's `setValue` function is called) and save the document. Until you do that for all documents, some documents will still have the old data.
To solve this, we export an `upgradeLexicalData` function which goes through every single document in your Payload app and re-saves it, if it has a lexical editor. This way, the data is automatically converted to the new format, and that automatic conversion gets applied to every single document in your app.
IMPORTANT: Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.
```ts
import { upgradeLexicalData } from '@payloadcms/richtext-lexical'
await upgradeLexicalData({ payload })
```
# Slate Editor
Source: https://payloadcms.com/docs/rich-text/slate
The [default Payload editor](../rich-text/overview) is currently based on Lexical. This documentation
is about our old Slate-based editor. You can continue using it because it is still supported, or you can
see the optional [migration guide](../rich-text/migration) to migrate from Slate to Lexical (recommended).
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'
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 type { CollectionConfig } from 'payload'
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`
- `li`
- `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](../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 with custom fields_
**`upload.collections[collection-name].fields`**
This allows [fields](../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 field using the upload element_

_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](../upload/overview) with a UI specifically designed for media / image-based uploads.
**Tip:**
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 **enableRichTextLink** and
**enableRichTextRelationship** to false.
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
```ts
import type { CollectionConfig } from 'payload'
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 = (
);
if (node.bold) {
text = {text};
}
if (node.code) {
text = {text};
}
if (node.italic) {
text = {text};
}
// Handle other leaf types here...
return {text};
}
if (!node) {
return null;
}
switch (node.type) {
case "h1":
return
{serialize(node.children)}
;
// Iterate through all headings here...
case "h6":
return
{serialize(node.children)}
;
case "blockquote":
return
{serialize(node.children)}
;
case "ul":
return
{serialize(node.children)}
;
case "ol":
return {serialize(node.children)};
case "li":
return
;
}
});
```
**Note:**
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!
### 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—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'
```
# Live Preview
Source: https://payloadcms.com/docs/live-preview/overview
With Live Preview you can render your front-end application directly within the [Admin Panel](../admin/overview). As you type, your changes take effect in real-time. No need to save a draft or publish your changes. This works in both [Server-side](./server) as well as [Client-side](./client) environments.
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.
To add Live Preview, use the `admin.livePreview` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// ...
// highlight-start
livePreview: {
url: 'http://localhost:3000',
collections: ['pages'],
},
// highlight-end
},
})
```
**Reminder:** Alternatively, you can define the `admin.livePreview` property
on individual [Collection Admin
Configs](../configuration/collections#admin-options) and [Global Admin
Configs](../configuration/globals#admin-options). Settings defined here will
be merged into the top-level as overrides.
## Options
Setting up Live Preview is easy. This can be done either globally through the [Root Admin Config](../admin/overview), or on individual [Collection Admin Configs](../configuration/collections#admin-options) and [Global Admin Configs](../configuration/globals#admin-options). Once configured, a new "Live Preview" button will appear at the top of enabled Documents. Toggling this button opens the preview window and loads your front-end application.
The following options are available:
| 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. |
### URL
The `url` property resolves to a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end. Once loaded, the Admin Panel will communicate directly with your app through `window.postMessage` events.
To set the URL, use the `admin.livePreview.url` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// ...
livePreview: {
url: 'http://localhost:3000', // highlight-line
collections: ['pages'],
},
},
})
```
#### Dynamic URLs
You can also pass a function in order to dynamically format URLs. This is useful for multi-tenant applications, localization, or any other scenario where the URL needs to be generated based on the Document being edited.
This is also useful for conditionally rendering Live Preview, similar to access control. See [Conditional Rendering](./conditional-rendering) for more details.
To set dynamic URLs, set the `admin.livePreview.url` property in your [Payload Config](../configuration/overview) to a function:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// ...
livePreview: {
// highlight-start
url: ({ data, collectionConfig, locale }) =>
`${data.tenant.url}${
collectionConfig.slug === 'posts'
? `/posts/${data.slug}`
: `${data.slug !== 'home' ? `/${data.slug}` : ''}`
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
collections: ['pages'],
},
// highlight-end
},
})
```
The following arguments are provided to the `url` function:
| Path | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **`data`** | The data of the Document being edited. This includes changes that have not yet been saved. |
| **`locale`** | The locale currently being edited (if applicable). [More details](../configuration/localization). |
| **`collectionConfig`** | The Collection Admin Config of the Document being edited. [More details](../configuration/collections#admin-options). |
| **`globalConfig`** | The Global Admin Config of the Document being edited. [More details](../configuration/globals#admin-options). |
| **`req`** | The Payload Request object. |
You can return either an absolute URL or relative URL from this function. If you don't know the URL of your frontend at build-time, you can return a relative URL, and in that case, Payload will automatically construct an absolute URL by injecting the protocol, domain, and port from your browser window. Returning a relative URL is helpful for platforms like Vercel where you may have preview deployment URLs that are unknown at build time.
If your application requires a fully qualified URL, or you are attempting to preview with a frontend on a different domain, you can use the `req` property to build this URL:
```ts
url: ({ data, req }) => `${req.protocol}//${req.host}/${data.slug}`
```
#### Conditional Rendering
You can conditionally render Live Preview by returning `undefined` or `null` from the `url` function. This is similar to access control, where you may want to restrict who can use Live Preview based on certain criteria, such as the current user or document data.
For example, you could check the user's role and only enable Live Preview if they have the appropriate permissions:
```ts
url: ({ req }) => (req.user?.role === 'admin' ? '/hello-world' : null)
```
### 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.
To set breakpoints, use the `admin.livePreview.breakpoints` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// ...
livePreview: {
url: 'http://localhost:3000',
// highlight-start
breakpoints: [
{
label: 'Mobile',
name: 'mobile',
width: 375,
height: 667,
},
],
// highlight-end
},
},
})
```
The following options are available for each breakpoint:
| 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._
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).
# Implementing Live Preview in your frontend
Source: https://payloadcms.com/docs/live-preview/frontend
There are two ways to use Live Preview in your own application depending on whether your front-end framework supports Server Components:
- [Server-side Live Preview (suggested)](./server)
- [Client-side Live Preview](./client)
We suggest using server-side Live Preview if your framework supports Server
Components, it is both simpler to setup and more performant to run than the
client-side alternative.
# Server-side Live Preview
Source: https://payloadcms.com/docs/live-preview/server
Server-side Live Preview is only for front-end frameworks that support the
concept of Server Components, i.e. [React Server
Components](https://react.dev/reference/rsc/server-components). If your
front-end application is built with a client-side framework like the [Next.js
Pages Router](https://nextjs.org/docs/pages), [React
Router](https://reactrouter.com), [Vue 3](https://vuejs.org), etc., see
[client-side Live Preview](./client).
Server-side Live Preview works by making a roundtrip to the server every time your document is saved, i.e. draft save, autosave, or publish. While using Live Preview, the Admin Panel emits a new `window.postMessage` event which your front-end application can use to invoke this process. In Next.js, this means simply calling `router.refresh()` which will hydrate the HTML using new data straight from the [Local API](../local-api/overview).
It is recommended that you enable [Autosave](../versions/autosave) alongside
Live Preview to make the experience feel more responsive.
If your front-end application is built with [React](#react), you can use the `RefreshRouteOnChange` function that Payload provides. In the future, all other major frameworks like Vue and 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 router refresh component](#building-your-own-router-refresh-component) for more information.
## React
If your front-end application is built with server-side [React](https://react.dev) like [Next.js App Router](https://nextjs.org/docs/app), you can use the `RefreshRouteOnSave` component that Payload provides.
First, install the `@payloadcms/live-preview-react` package:
```bash
npm install @payloadcms/live-preview-react
```
Then, render the `RefreshRouteOnSave` component anywhere in your `page.tsx`. Here's an example:
`page.tsx`:
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
import { getPayload } from 'payload'
import config from '../payload.config'
export default async function Page() {
const payload = await getPayload({ config })
const page = await payload.findByID({
collection: 'pages',
id: '123',
draft: true,
trash: true, // add this if trash is enabled in your collection and want to preview trashed documents
})
return (
{page.title}
)
}
```
`RefreshRouteOnSave.tsx`:
```tsx
'use client'
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react'
import { useRouter } from 'next/navigation.js'
import React from 'react'
export const RefreshRouteOnSave: React.FC = () => {
const router = useRouter()
return (
router.refresh()}
serverURL={process.env.NEXT_PUBLIC_PAYLOAD_URL}
/>
)
}
```
## Building your own router refresh component
No matter what front-end framework you are using, you can build your own component 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 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **`ready`** | Sends a `window.postMessage` event to the Admin Panel to indicate that the front-end is ready to receive messages. |
| **`isDocumentEvent`** | Checks if a `MessageEvent` originates from the Admin Panel and is a document-level event, i.e. draft save, autosave, publish, etc. |
With these functions, you can build your own hook using your front-end framework of choice:
```tsx
import { ready, isDocumentEvent } from '@payloadcms/live-preview'
// To build your own component:
// 1. Listen for document-level `window.postMessage` events sent from the Admin Panel
// 2. Tell the Admin Panel when it is ready to receive messages
// 3. Refresh the route every time a new document-level event is received
// 4. Unsubscribe from the `window.postMessage` events when it unmounts
```
Here is an example of what the same `RefreshRouteOnSave` React component from above looks like under the hood:
```tsx
'use client'
import type React from 'react'
import { isDocumentEvent, ready } from '@payloadcms/live-preview'
import { useCallback, useEffect, useRef } from 'react'
export const RefreshRouteOnSave: React.FC<{
apiRoute?: string
depth?: number
refresh: () => void
serverURL: string
}> = (props) => {
const { apiRoute, depth, refresh, serverURL } = props
const hasSentReadyMessage = useRef(false)
const onMessage = useCallback(
(event: MessageEvent) => {
if (isDocumentEvent(event, serverURL)) {
if (typeof refresh === 'function') {
refresh()
}
}
},
[refresh, serverURL],
)
useEffect(() => {
if (typeof window !== 'undefined') {
window.addEventListener('message', onMessage)
}
if (!hasSentReadyMessage.current) {
hasSentReadyMessage.current = true
ready({
serverURL,
})
}
return () => {
if (typeof window !== 'undefined') {
window.removeEventListener('message', onMessage)
}
}
}, [serverURL, onMessage, depth, apiRoute])
return null
}
```
## Example
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview). There you will find a fully working example of how to implement Live Preview in your Next.js App Router application.
## Troubleshooting
#### Updates do not appear as fast as client-side Live Preview
If you are noticing that updates feel less snappy than client-side Live Preview (i.e. the `useLivePreview` hook), this is because of how the two differ in how they work—instead of emitting events against _form state_, server-side Live Preview refreshes the route after a new document is _saved_.
Use [Autosave](../versions/autosave) to mimic this effect server-side. Try decreasing the value of `versions.autoSave.interval` to make the experience feel more responsive:
```ts
// collection.ts
{
versions: {
drafts: {
autosave: {
interval: 375,
},
},
},
}
```
#### 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;
```
# Client-side Live Preview
Source: https://payloadcms.com/docs/live-preview/client
If your front-end application supports Server Components like the [Next.js App
Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side
Live Preview](./server) instead.
While using Live Preview, the [Admin Panel](../admin/overview) emits a new `window.postMessage` event every time your document has changed. Your front-end application can listen for these events and re-render accordingly.
If your front-end application is built with [React](#react) or [Vue](#vue), use the `useLivePreview` hooks 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. |
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`.
It 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](../queries/depth) for
more information.
## Frameworks
Live Preview will work with any front-end framework that supports the native [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) API. By default, Payload officially supports the most popular frameworks, including:
- [React](#react)
- [Vue](#vue)
If your framework is not listed, you can still integrate with Live Preview using the underlying tooling that Payload provides. [More details](#building-your-own-hook).
### React
If your front-end application is built with client-side [React](https://react.dev) like [Next.js Pages Router](https://nextjs.org/docs/pages), 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({
initialData: initialPage,
serverURL: PAYLOAD_SERVER_URL,
depth: 2,
})
return
{data.title}
}
```
**Reminder:** If you are using [React Server
Components](https://react.dev/reference/rsc/server-components), we strongly
suggest setting up [server-side Live Preview](./server) instead.
### Vue
If your front-end application is built with [Vue 3](https://vuejs.org) or [Nuxt 3](https://nuxt.js), 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:
```ts
{{ data.title }}
```
## 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. |
| **`isLivePreviewEvent`** | Checks if a `MessageEvent` originates from the Admin Panel and is a Live Preview event, i.e. debounced form state. |
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 = (props: {
depth?: number
initialData: T
serverURL: string
}): {
data: T
isLoading: boolean
} => {
const { depth = 0, initialData, serverURL } = props
const [data, setData] = useState(initialData)
const [isLoading, setIsLoading] = useState(true)
const hasSentReadyMessage = useRef(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,
}
}
```
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.
## Example
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview). There you will find an example of a fully integrated Next.js App Router front-end that runs on the same server as Payload.
## 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#cors) 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/cookies#csrf-prevention) 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 allow 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`](../queries/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({
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;
```
# Versions
Source: https://payloadcms.com/docs/versions/overview
Payload's powerful Versions functionality allows you to keep a running history
of changes over time and extensible to fit any content publishing workflow.
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.

_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](../versions/drafts) mode for your data
- Manage who can see Drafts, and who can only see Published documents via [Access Control](../access-control/overview)
- Enable [Autosave](../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
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 risk.
## 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](../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](../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](../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](../versions/drafts) mode for this collection. To enable, set to `true` or pass an object with `draft` [options](../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](../versions/drafts) mode for this global. To enable, set to `true` or pass an object with `draft` [options](../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](../access-control/overview) function on both [Collections](../configuration/collections) and [Globals](../configuration/globals) that allow for you to control who can see versions of documents, and who can't.
| 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. |
For full details on how to use Access Control with Versions, see the [Access Control](../access-control/overview) documentation.
# Drafts
Source: https://payloadcms.com/docs/versions/drafts
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.
Drafts rely on Versions being enabled in order to function.
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.

_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](../versions/autosave). |
| `schedulePublish` | Allow for editors to schedule publish / unpublish events in the future. [More](#scheduled-publish) |
| `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 **automatically inject a new field into your schema** 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. **Draft** - if a document has never been published, and only draft versions of the document
are present
1. **Published** - if a document is published and there are no newer drafts available
1. **Changed** - if a document has been published, but there are newer drafts available
and not yet published
## Draft API
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.
#### 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 **draft**, or if you're just sending your changes straight to the published document.
For example:
```ts
// REST API
POST /api/your-collection?draft=true
// Local API
await payload.create({
collection: 'your-collection',
data: {
// your data here
},
draft: true, // This is required to create a draft
})
// GraphQL
mutation {
createYourCollection(data: { ... }, draft: true) {
// ...
}
}
```
**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.
Setting `_status: "draft"` will not bypass the required fields. You need to set `draft: true` as shown in the previous examples.
#### 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, **Payload will automatically replace returned document(s) with their newest drafts** if any newer drafts are available.
**For example, let's take the following scenario:**
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.
**Important:** the `draft` argument on its own will not restrict documents
with `_status: 'draft'` from being returned from the API. You need to use
Access Control to prevent documents with `_status: 'draft'` from being
returned to unauthenticated users. Read below for more information on how this
works.
## Controlling who can see Collection drafts
If you're using the **drafts** 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.
#### Restricting draft access
You can use the `read` [Access Control](../access-control/collections#read) method to restrict who is able to view drafts of your documents by simply returning a [query constraint](../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 type { CollectionConfig } from 'payload'
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
}
```
**Note regarding adding versions to an existing collection**
If you already have a collection with documents, and you _opt in_ to draft functionality
after you have already created existing documents, all of your old documents
_will not have a `_status` field_ until you resave them. For this reason, if you are
_adding_ versions into an existing collection, you might want to write your Access Control
function to allow for users to read both documents where
**`_status` is equal to `"published"`** as well as where
**`_status` does not exist**.
Here is an example for how to write an [Access Control](../access-control/overview) function that grants access to both documents where `_status` is equal to "published" and where `_status` does not exist:
```ts
import type { CollectionConfig } from 'payload'
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
}
```
## Scheduled publish
Payload provides for an ability to schedule publishing / unpublishing events in the future, which can be helpful if you need to set certain documents to "go live" at a given date in the future, or, vice versa, revert to a draft state after a certain time has passed.
You can enable this functionality on both collections and globals via the `versions.drafts.schedulePublish: true` property.
**Important:** if you are going to enable scheduled publish / unpublish, you
need to make sure your Payload app is set up to process
[Jobs](../jobs-queue/overview). This feature works by creating a Job in the
background, which will be picked up after the job becomes available. If you do
not have any mechanism in place to run jobs, your scheduled publish /
unpublish jobs will never be executed.
## 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.
# Autosave
Source: https://payloadcms.com/docs/versions/autosave
Extending on Payload's [Draft](../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.
Autosave relies on Versions and Drafts being enabled in order to function.

_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`. |
| `showSaveDraftButton` | Set this to `true` to show the "Save as draft" button even while autosave is enabled. Defaults to `false`. |
**Example config with versions, drafts, and autosave enabled:**
```ts
import type { CollectionConfig } from 'payload'
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 object to customize autosave:
// autosave: {
// Define how often the document should be autosaved (in milliseconds)
// interval: 1500,
//
// Show the "Save as draft" button even while autosave is enabled
// showSaveDraftButton: true,
// },
},
},
//.. 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.
Instead of creating a new version each time a document is autosaved, Payload
smartly only creates **one** autosave version, and then updates that specific
version with each autosave performed. This makes sure that your versions
remain nice and tidy.
# Uploads
Source: https://payloadcms.com/docs/upload/overview
Payload provides everything you need to enable file upload, storage, and
management directly on your server—including extremely powerful file [access
control](#access-control).
**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.
**Tip:**
A common pattern is to create a **"media"** collection and enable **upload** on that collection.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
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',
},
],
}
```
### Collection Upload Options
_An asterisk denotes that an option is required._
| Option | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true |
| **`cacheTags`** | Set to `false` to disable the cache tag set in the UI for the admin thumbnail component. Useful for when CDNs don't allow certain cache queries. |
| **`constructorOptions`** | An object passed to the the Sharp image library that accepts any Constructor options and applies them to the upload file. [More](https://sharp.pixelplumbing.com/api-constructor/) |
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). 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](../fields/upload#config-options). |
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. If using this option, you should handle the removal of any sensitive cookies (like payload-prefixed cookies) to prevent leaking session information to external services. By default, Payload automatically filters out payload-prefixed cookies when this option is not defined. |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
| **`filenameCompoundIndex`** | Field slugs to use for a compound index instead of the default filename index. |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`handlers`** | Array of Request handlers to execute when fetching a file, if a handler returns a Response it will be sent to the client. Otherwise Payload will retrieve and send back the file. |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`pasteURL`** | Controls whether files can be uploaded from remote URLs by pasting them into the Upload field. **Enabled by default.** Accepts `false` to disable or an object with an `allowList` of valid remote URLs. [More](#uploading-files-from-remote-urls) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`skipSafeFetch`** | Set to an `allowList` to skip the safe fetch check when fetching external files. Set to `true` to skip the safe fetch for all documents in this collection. Defaults to `false`. |
| **`allowRestrictedFileTypes`** | Set to `true` to allow restricted file types. If your Collection has defined [mimeTypes](#mimetypes), restricted file verification will be skipped. Defaults to `false`. [More](#restricted-file-types) |
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
| **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. |
| **`hideFileInputOnCreate`** | Set to `true` to prevent the admin UI from showing file inputs during document creation, useful for programmatic file generation. |
| **`hideRemoveFile`** | Set to `true` to prevent the admin UI having a way to remove an existing file while editing. |
| **`modifyResponseHeaders`** | Accepts an object with existing `headers` and allows you to manipulate the response headers for media files. [More](#modifying-response-headers) |
### Payload-wide Upload Options
Upload options are specifiable on a Collection by Collection basis, you can also control app wide options by passing your base Payload Config an `upload` property containing an object supportive of all `Busboy` configuration options.
| Option | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`abortOnLimit`** | A boolean that, if `true`, returns HTTP 413 if a file exceeds the file size limit. If `false`, the file is truncated. Defaults to `false`. |
| **`createParentPath`** | Set to `true` to automatically create a directory path when moving files from a temporary directory or buffer. Defaults to `false`. |
| **`debug`** | A boolean that turns upload process logging on if `true`, or off if `false`. Useful for troubleshooting. Defaults to `false`. |
| **`limitHandler`** | A function which is invoked if the file is greater than configured limits. |
| **`parseNested`** | Set to `true` to turn `req.body` and `req.files` into nested structures. By default `req.body` and `req.files` are flat objects. Defaults to `false`. |
| **`preserveExtension`** | Preserves file extensions with the `safeFileNames` option. Limits file names to 3 characters if `true` or a custom length if a `number`, trimming from the start of the extension. |
| **`responseOnLimit`** | A `string` that is sent in the Response to a client if the file size limit is exceeded when used with `abortOnLimit`. |
| **`safeFileNames`** | Set to `true` to strip non-alphanumeric characters except dashes and underscores. Can also be set to a regex to determine what to strip. Defaults to `false`. |
| **`tempFileDir`** | A `string` path to store temporary files used when the `useTempFiles` option is set to `true`. Defaults to `'./tmp'`. |
| **`uploadTimeout`** | A `number` that defines how long to wait for data before aborting, specified in milliseconds. Set to `0` to disable timeout checks. Defaults to `60000`. |
| **`uriDecodeFileNames`** | Set to `true` to apply uri decoding to file names. Defaults to `false`. |
| **`useTempFiles`** | Set to `true` to store files to a temporary directory instead of in RAM, reducing memory usage for large files or many files. |
[Click here](https://github.com/mscdex/busboy#api) for more documentation about what you can control with `Busboy`.
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'
export default buildConfig({
collections: [
{
slug: 'media',
fields: [
{
name: 'alt',
type: 'text',
},
],
upload: true,
},
],
upload: {
limits: {
fileSize: 5000000, // 5MB, written in bytes
},
},
})
```
### Custom filename via hooks
You can customize the filename before it's uploaded to the server by using a `beforeOperation` hook.
```ts
beforeOperation: [
({ req, operation }) => {
if ((operation === 'create' || operation === 'update') && req.file) {
req.file.name = 'test.jpg'
}
},
],
```
The `req.file` object will have additional information about the file, such as mimeType and extension, and you also have full access to the file data itself.
The filename from here will also be threaded to image sizes if they're enabled.
## 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 [Admin Panel](../admin/overview) will also automatically display all available files, including width, height, and file size, 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.
Note that for image resizing to work, `sharp` must be specified in your [Payload Config](../configuration/overview). This is configured by default if you created your Payload project with `create-payload-app`. See `sharp` in [Config Options](../configuration/overview#config-options).
#### Admin List View Options
Each image size also supports `admin` options to control whether it appears in the [Admin Panel](../admin/overview) list view.
```ts
{
name: 'thumbnail',
width: 400,
height: 300,
admin: {
disableGroupBy: true, // hide from list view groupBy options
disableListColumn: true, // hide from list view columns
disableListFilter: true, // hide from list view filters
},
}
```
| Option | Description |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **`disableGroupBy`** | If set to `true`, this image size will not be available as a selectable groupBy option in the collection list view. Defaults to `false`. |
| **`disableListColumn`** | If set to `true`, this image size will not be available as a selectable column in the collection list view. Defaults to `false`. |
| **`disableListFilter`** | If set to `true`, this image size will not be available as a filter option in the collection list view. Defaults to `false`. |
This is useful for hiding large or rarely used image sizes from the list view UI while still keeping them available in the API.
#### 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
**Note:**
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.
#### 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](../admin/overview).
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.
**Note:**
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 **upload.adminThumbnail**.
## Admin Thumbnails
You can specify how Payload retrieves admin thumbnails for your upload-enabled Collections with one of the following:
1. `adminThumbnail` as a **string**, equal to one of your provided image size names.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
// highlight-start
adminThumbnail: 'small',
// highlight-end
imageSizes: [
{
name: 'small',
fit: 'cover',
height: 300,
width: 900,
},
{
name: 'large',
fit: 'cover',
height: 600,
width: 1800,
},
],
},
}
```
2. `adminThumbnail` as a **function** that takes the document's data and sends back a full URL to load the thumbnail.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
// highlight-start
adminThumbnail: ({ doc }) =>
`https://google.com/custom-path-to-file/${doc.filename}`,
// highlight-end
},
}
```
## Restricted File Types
Possibly problematic file types are automatically restricted from being uploaded to your application.
If your Collection has defined [mimeTypes](#mimetypes) or has set `allowRestrictedFileTypes` to `true`, restricted file verification will be skipped.
Restricted file types and extensions:
| File Extensions | MIME Type |
| ------------------------------------ | ----------------------------------------------- |
| `exe`, `dll` | `application/x-msdownload` |
| `exe`, `com`, `app`, `action` | `application/x-executable` |
| `bat`, `cmd` | `application/x-msdos-program` |
| `exe`, `com` | `application/x-ms-dos-executable` |
| `dmg` | `application/x-apple-diskimage` |
| `deb` | `application/x-debian-package` |
| `rpm` | `application/x-redhat-package-manager` |
| `exe`, `dll` | `application/vnd.microsoft.portable-executable` |
| `msi` | `application/x-msi` |
| `jar`, `ear`, `war` | `application/java-archive` |
| `desktop` | `application/x-desktop` |
| `cpl` | `application/x-cpl` |
| `lnk` | `application/x-ms-shortcut` |
| `pkg` | `application/x-apple-installer` |
| `htm`, `html`, `shtml`, `xhtml` | `text/html` |
| `php`, `phtml` | `application/x-httpd-php` |
| `js`, `jse` | `text/javascript` |
| `jsp` | `application/x-jsp` |
| `py` | `text/x-python` |
| `rb` | `text/x-ruby` |
| `pl` | `text/x-perl` |
| `ps1`, `psc1`, `psd1`, `psh`, `psm1` | `application/x-powershell` |
| `vbe`, `vbs` | `application/x-vbscript` |
| `ws`, `wsc`, `wsf`, `wsh` | `application/x-ms-wsh` |
| `scr` | `application/x-msdownload` |
| `asp`, `aspx` | `application/x-asp` |
| `hta` | `application/x-hta` |
| `reg` | `application/x-registry` |
| `url` | `application/x-url` |
| `workflow` | `application/x-workflow` |
| `command` | `application/x-command` |
## 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 type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
mimeTypes: ['image/*', 'application/pdf'], // highlight-line
},
}
```
## Uploading Files
**Important:**
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.
To upload a file, use your collection's [`create`](../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.
**Note:** To include any additional fields (like `title`, `alt`, etc.), append
a `_payload` field containing a JSON-stringified object of the required
values. These values must match the schema of your upload-enabled collection.
```ts
const fileInput = document.querySelector('#your-file-input')
const formData = new FormData()
formData.append('file', fileInput.files[0])
// Replace with the fields defined in your upload-enabled collection.
// The example below includes an optional field like 'title'.
formData.append(
'_payload',
JSON.stringify({
title: 'Example Title',
description: 'An optional description for the file',
}),
)
fetch('api/:upload-slug', {
method: 'POST',
body: formData,
/**
* Do not manually add the Content-Type Header
* the browser will handle this.
*
* headers: {
* 'Content-Type': 'multipart/form-data'
* }
*/
})
```
## Uploading Files stored locally
If you want to upload a file stored on your machine directly using the `payload.create` method, for example, during a seed script,
you can use the `filePath` property to specify the local path of the file.
```ts
const localFilePath = path.resolve(__dirname, filename)
await payload.create({
collection: 'media',
data: {
alt,
},
filePath: localFilePath,
})
```
The `data` property should still include all the required fields of your `media` collection.
**Important:**
Remember that all custom hooks attached to the `media` collection will still trigger.
Ensure that files match the specified mimeTypes or sizes defined in the collection's `formatOptions` or custom `hooks`.
## Uploading Files from Remote URLs
The `pasteURL` option allows users to fetch files from remote URLs by pasting them into an Upload field. This option is **enabled by default** and can be configured to either **allow unrestricted client-side fetching** or **restrict server-side fetching** to specific trusted domains.
By default, Payload uses **client-side fetching**, where the browser downloads the file directly from the provided URL. However, **client-side fetching will fail if the URL’s server has CORS restrictions**, making it suitable only for internal URLs or public URLs without CORS blocks.
To fetch files from **restricted URLs** that would otherwise be blocked by CORS, use **server-side fetching** by configuring the `pasteURL` option with an `allowList` of trusted domains. This method ensures that Payload downloads the file on the server and streams it to the browser. However, for security reasons, only URLs that match the specified `allowList` will be allowed.
#### Configuration Example
Here’s how to configure the pasteURL option to control remote URL fetching:
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
pasteURL: {
allowList: [
{
hostname: 'payloadcms.com', // required
pathname: '',
port: '',
protocol: 'https',
search: '',
},
{
hostname: 'example.com',
pathname: '/images/*',
},
],
},
},
}
```
You can also adjust server-side fetching at the upload level as well, this does not effect the `CORS` policy like the `pasteURL` option does, but it allows you to skip the safe fetch check for specific URLs.
```
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
skipSafeFetch: [
{
hostname: 'example.com',
pathname: '/images/*',
},
],
},
}
```
##### Accepted Values for `pasteURL`
| Option | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`undefined`** | Default behavior. Enables client-side fetching for internal or public URLs. |
| **`false`** | Disables the ability to paste URLs into Upload fields. |
| **`allowList`** | Enables server-side fetching for specific trusted URLs. Requires an array of objects defining trusted domains. See the table below for details on `AllowItem`. |
##### `AllowItem` Properties
_An asterisk denotes that an option is required._
| Option | Description | Example |
| ----------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| **`hostname`** \* | The hostname of the allowed URL. This is required to ensure the URL is coming from a trusted source. | `example.com` |
| **`pathname`** | The path portion of the URL. Supports wildcards to match multiple paths. | `/images/*` |
| **`port`** | The port number of the URL. If not specified, the default port for the protocol will be used. | `3000` |
| **`protocol`** | The protocol to match. Must be either `http` or `https`. Defaults to `https`. | `https` |
| **`search`** | The query string of the URL. If specified, the URL must match this exact query string. | `?version=1` |
## Access Control
All files that are uploaded to each Collection automatically support the `read` [Access Control](../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.
## Modifying response headers
You can modify the response headers for files by specifying the `modifyResponseHeaders` option in your upload config. This option accepts an object with existing headers and allows you to manipulate the response headers for media files.
### Modifying existing headers
With this method you can directly interface with the `Headers` object and modify the existing headers to append or remove headers.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
modifyResponseHeaders: ({ headers }) => {
headers.set('X-Frame-Options', 'DENY') // You can directly set headers without returning
},
},
}
```
### Return new headers
You can also return a new `Headers` object with the modified headers. This is useful if you want to set new headers or remove existing ones.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
modifyResponseHeaders: ({ headers }) => {
const newHeaders = new Headers(headers) // Copy existing headers
newHeaders.set('X-Frame-Options', 'DENY') // Set new header
return newHeaders
},
},
}
```
# Storage Adapters
Source: https://payloadcms.com/docs/upload/storage-adapters
Payload offers additional storage adapters to handle file uploads. These adapters allow you to store files in different locations, such as Amazon S3, Vercel Blob Storage, Google Cloud Storage, and more.
| Service | Package |
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Vercel Blob | [`@payloadcms/storage-vercel-blob`](https://github.com/payloadcms/payload/tree/main/packages/storage-vercel-blob) |
| AWS S3 | [`@payloadcms/storage-s3`](https://github.com/payloadcms/payload/tree/main/packages/storage-s3) |
| Azure | [`@payloadcms/storage-azure`](https://github.com/payloadcms/payload/tree/main/packages/storage-azure) |
| Google Cloud Storage | [`@payloadcms/storage-gcs`](https://github.com/payloadcms/payload/tree/main/packages/storage-gcs) |
| Uploadthing | [`@payloadcms/storage-uploadthing`](https://github.com/payloadcms/payload/tree/main/packages/storage-uploadthing) |
| R2 | [`@payloadcms/storage-r2`](https://github.com/payloadcms/payload/tree/main/packages/storage-r2) |
## Vercel Blob Storage
[`@payloadcms/storage-vercel-blob`](https://www.npmjs.com/package/@payloadcms/storage-vercel-blob)
### Installation#vercel-blob-installation
```sh
pnpm add @payloadcms/storage-vercel-blob
```
### Usage#vercel-blob-usage
- Configure the `collections` object to specify which collections should use the Vercel Blob adapter. The slug _must_ match one of your existing collection slugs.
- Ensure you have `BLOB_READ_WRITE_TOKEN` set in your Vercel environment variables. This is usually set by Vercel automatically after adding blob storage to your project.
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client.
```ts
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'
export default buildConfig({
collections: [Media, MediaWithPrefix],
plugins: [
vercelBlobStorage({
enabled: true, // Optional, defaults to true
// Specify which collections should use Vercel Blob
collections: {
media: true,
'media-with-prefix': {
prefix: 'my-prefix',
},
},
// Token provided by Vercel once Blob storage is added to your Vercel project
token: process.env.BLOB_READ_WRITE_TOKEN,
}),
],
})
```
### Configuration Options#vercel-blob-configuration
| Option | Description | Default |
| -------------------- | -------------------------------------------------------------------- | ----------------------------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Vercel Blob adapter to | |
| `addRandomSuffix` | Add a random suffix to the uploaded file name in Vercel Blob storage | `false` |
| `cacheControlMaxAge` | Cache-Control max-age in seconds | `365 * 24 * 60 * 60` (1 Year) |
| `token` | Vercel Blob storage read/write token | `''` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
## S3 Storage
[`@payloadcms/storage-s3`](https://www.npmjs.com/package/@payloadcms/storage-s3)
### Installation#s3-installation
```sh
pnpm add @payloadcms/storage-s3
```
### Usage#s3-usage
- Configure the `collections` object to specify which collections should use the S3 Storage adapter. The slug _must_ match one of your existing collection slugs.
- The `config` object can be any [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object (from [`@aws-sdk/client-s3`](https://github.com/aws/aws-sdk-js-v3)). _This is highly dependent on your AWS setup_. Check the AWS documentation for more information.
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
- Configure `signedDownloads` (either globally of per-collection in `collections`) to use [presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) for files downloading. This can improve performance for large files (like videos) while still respecting your access control. Additionally, with `signedDownloads.shouldUseSignedURL` you can specify a condition whether Payload should use a presigned URL, if you want to use this feature only for specific files.
```ts
import { s3Storage } from '@payloadcms/storage-s3'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'
export default buildConfig({
collections: [Media, MediaWithPrefix],
plugins: [
s3Storage({
collections: {
media: true,
'media-with-prefix': {
prefix,
},
'media-with-presigned-downloads': {
// Filter only mp4 files
signedDownloads: {
shouldUseSignedURL: ({ collection, filename, req }) => {
return filename.endsWith('.mp4')
},
},
},
},
bucket: process.env.S3_BUCKET,
config: {
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
region: process.env.S3_REGION,
// ... Other S3 configuration
},
}),
],
})
```
### Configuration Options#s3-configuration
See the the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object for guidance on AWS S3 configuration.
## Azure Blob Storage
[`@payloadcms/storage-azure`](https://www.npmjs.com/package/@payloadcms/storage-azure)
### Installation#azure-installation
```sh
pnpm add @payloadcms/storage-azure
```
### Usage#azure-usage
- Configure the `collections` object to specify which collections should use the Azure Blob adapter. The slug _must_ match one of your existing collection slugs.
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method to your website.
```ts
import { azureStorage } from '@payloadcms/storage-azure'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'
export default buildConfig({
collections: [Media, MediaWithPrefix],
plugins: [
azureStorage({
collections: {
media: true,
'media-with-prefix': {
prefix,
},
},
allowContainerCreate:
process.env.AZURE_STORAGE_ALLOW_CONTAINER_CREATE === 'true',
baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
}),
],
})
```
### Configuration Options#azure-configuration
| Option | Description | Default |
| ---------------------- | ------------------------------------------------------------------------ | ------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Azure Blob adapter to | |
| `allowContainerCreate` | Whether or not to allow the container to be created if it does not exist | `false` |
| `baseURL` | Base URL for the Azure Blob storage account | |
| `connectionString` | Azure Blob storage connection string | |
| `containerName` | Azure Blob storage container name | |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
## Google Cloud Storage
[`@payloadcms/storage-gcs`](https://www.npmjs.com/package/@payloadcms/storage-gcs)
### Installation#gcs-installation
```sh
pnpm add @payloadcms/storage-gcs
```
### Usage#gcs-usage
- Configure the `collections` object to specify which collections should use the Google Cloud Storage adapter. The slug _must_ match one of your existing collection slugs.
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
```ts
import { gcsStorage } from '@payloadcms/storage-gcs'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'
export default buildConfig({
collections: [Media, MediaWithPrefix],
plugins: [
gcsStorage({
collections: {
media: true,
'media-with-prefix': {
prefix,
},
},
bucket: process.env.GCS_BUCKET,
options: {
apiEndpoint: process.env.GCS_ENDPOINT,
projectId: process.env.GCS_PROJECT_ID,
},
}),
],
})
```
### Configuration Options#gcs-configuration
| Option | Description | Default |
| --------------- | --------------------------------------------------------------------------------------------------- | --------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the storage to | |
| `bucket` | The name of the bucket to use | |
| `options` | Google Cloud Storage client configuration. See [Docs](https://github.com/googleapis/nodejs-storage) | |
| `acl` | Access control list for files that are uploaded | `Private` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
## Uploadthing Storage
[`@payloadcms/storage-uploadthing`](https://www.npmjs.com/package/@payloadcms/storage-uploadthing)
### Installation#uploadthing-installation
```sh
pnpm add @payloadcms/storage-uploadthing
```
### Usage#uploadthing-usage
- Configure the `collections` object to specify which collections should use uploadthing. The slug _must_ match one of your existing collection slugs and be an `upload` type.
- Get a token from Uploadthing and set it as `token` in the `options` object.
- `acl` is optional and defaults to `public-read`.
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client.
```ts
export default buildConfig({
collections: [Media],
plugins: [
uploadthingStorage({
collections: {
media: true,
},
options: {
token: process.env.UPLOADTHING_TOKEN,
acl: 'public-read',
},
}),
],
})
```
### Configuration Options#uploadthing-configuration
| Option | Description | Default |
| ---------------- | ------------------------------------------------------------- | ------------- |
| `token` | Token from Uploadthing. Required. | |
| `acl` | Access control list for files that are uploaded | `public-read` |
| `logLevel` | Log level for Uploadthing | `info` |
| `fetch` | Custom fetch function | `fetch` |
| `defaultKeyType` | Default key type for file operations | `fileKey` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
## R2 Storage
**Note**: The R2 Storage Adapter is in **beta** as some aspects of it may
change on any minor releases.
[`@payloadcms/storage-r2`](https://www.npmjs.com/package/@payloadcms/storage-r2)
Use this adapter to store uploads in a Cloudflare R2 bucket via the Cloudflare Workers environment. If you're trying to connect to R2 using the S3 API then you should use the [S3](#s3-storage) adapter instead.
### Installation#r2-installation
```sh
pnpm add @payloadcms/storage-r2
```
### Usage#r2-usage
- Configure the `collections` object to specify which collections should use r2. The slug _must_ match one of your existing collection slugs and be an `upload` type.
- Pass in the R2 bucket binding to the `bucket` option, this should be done in the environment where Payload is running (e.g. Cloudflare Worker).
- You can conditionally datamine whether or not to enable the plugin with the `enabled` option.
```ts
export default buildConfig({
collections: [Media],
plugins: [
r2Storage({
collections: {
media: true,
},
bucket: cloudflare.env.R2,
}),
],
})
```
## Custom Storage Adapters
If you need to create a custom storage adapter, you can use the [`@payloadcms/plugin-cloud-storage`](https://www.npmjs.com/package/@payloadcms/plugin-cloud-storage) package. This package is used internally by the storage adapters mentioned above.
### Installation#custom-installation
`pnpm add @payloadcms/plugin-cloud-storage`
### Usage#custom-usage
Reference any of the existing storage adapters for guidance on how this should be structured. Create an adapter following the `GeneratedAdapter` interface. Then, pass the adapter to the `cloudStorage` plugin.
```ts
export interface GeneratedAdapter {
/**
* Additional fields to be injected into the base
* collection and image sizes
*/
fields?: Field[]
/**
* Generates the public URL for a file
*/
generateURL?: GenerateURL
handleDelete: HandleDelete
handleUpload: HandleUpload
name: string
onInit?: () => void
staticHandler: StaticHandler
}
```
```ts
import { buildConfig } from 'payload'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
export default buildConfig({
plugins: [
cloudStorage({
collections: {
'my-collection-slug': {
adapter: theAdapterToUse, // see docs for the adapter you want to use
},
},
}),
],
// The rest of your config goes here
})
```
## Plugin options
This plugin is configurable to work across many different Payload collections. A `*` denotes that the property is required.
| Option | Type | Description |
| ---------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `collections` \* | `Record` | Object with keys set to the slug of collections you want to enable the plugin for, and values set to collection-specific options. |
| `enabled` | `boolean` | To conditionally enable/disable plugin. Default: `true`. |
## Collection-specific options
| Option | Type | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adapter` \* | [Adapter](https://github.com/payloadcms/payload/blob/main/packages/plugin-cloud-storage/src/types.ts#L49) | Pass in the adapter that you'd like to use for this collection. You can also set this field to `null` for local development if you'd like to bypass cloud storage in certain scenarios and use local storage. |
| `disableLocalStorage` | `boolean` | Choose to disable local storage on this collection. Defaults to `true`. |
| `disablePayloadAccessControl` | `true` | Set to `true` to disable Payload's Access Control. [More](#payload-access-control) |
| `prefix` | `string` | Set to `media/images` to upload files inside `media/images` folder in the bucket. |
| `generateFileURL` | [GenerateFileURL](https://github.com/payloadcms/payload/blob/main/packages/plugin-cloud-storage/src/types.ts#L67) | Override the generated file URL with one that you create. |
## Payload Access Control
Payload ships with [Access Control](../access-control/overview) that runs _even on statically served files_. The same `read` Access Control property on your `upload`-enabled collections is used, and it allows you to restrict who can request your uploaded files.
To preserve this feature, by default, this plugin _keeps all file URLs exactly the same_. Your file URLs won't be updated to point directly to your cloud storage source, as in that case, Payload's Access control will be completely bypassed and you would need public readability on your cloud-hosted files.
Instead, all uploads will still be reached from the default `/collectionSlug/staticURL/filename` path. This plugin will "pass through" all files that are hosted on your third-party cloud service—with the added benefit of keeping your existing Access Control in place.
If this does not apply to you (your upload collection has `read: () => true` or similar) you can disable this functionality by setting `disablePayloadAccessControl` to `true`. When this setting is in place, this plugin will update your file URLs to point directly to your cloud host.
## Conditionally Enabling/Disabling
The proper way to conditionally enable/disable this plugin is to use the `enabled` property.
```ts
cloudStoragePlugin({
enabled: process.env.MY_CONDITION === 'true',
collections: {
'my-collection-slug': {
adapter: theAdapterToUse, // see docs for the adapter you want to use
},
},
}),
```
# Folders
Source: https://payloadcms.com/docs/folders/overview
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
**Note:** The Folders feature is currently in beta and may be subject to
change in minor versions updates prior to being stable.
## Folder Configuration
On the payload config, you can configure the following settings under the `folders` property:
```ts
// Type definition
type RootFoldersConfiguration = {
/**
* If true, the browse by folder view will be enabled
*
* @default true
*/
browseByFolder?: boolean
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
*/
collectionOverrides?: (({
collection,
}: {
collection: CollectionConfig
}) => CollectionConfig | Promise)[]
/**
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* The Folder field name
*
* @default "folder"
*/
fieldName?: string
/**
* Slug for the folder collection
*
* @default "payload-folders"
*/
slug?: string
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
folders: {
// highlight-start
debug: true, // optional
collectionOverrides: [
async ({ collection }) => {
return collection
},
], // optional
fieldName: 'folder', // optional
slug: 'payload-folders', // optional
// highlight-end
},
})
```
## Collection Configuration
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
```ts
// Type definition
type CollectionFoldersConfiguration =
| boolean
| {
/**
* If true, the collection will be included in the browse by folder view
*
* @default true
*/
browseByFolder?: boolean
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
collections: [
{
slug: 'pages',
// highlight-start
folders: true, // defaults to false
// highlight-end
},
],
})
```
# Email Functionality
Source: https://payloadcms.com/docs/email/overview
## Introduction
Payload has a few email adapters that can be imported to enable email functionality. The [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) package will be the package most will want to install. This package provides an easy way to use [Nodemailer](https://nodemailer.com) for email and won't get in your way for those already familiar.
The email adapter should be passed into the `email` property of the Payload Config. This will allow Payload to send [auth-related emails](../authentication/email) for things like password resets, new user verification, and any other email sending needs you may have.
## Configuration
### Default Configuration
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
### Email Adapter
An email adapter will require at least the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| **`defaultFromName`** \* | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** \* | The email address part of the From field that will be used when delivering email |
### Official Email Adapters
| Name | Package | Description |
| ---------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nodemailer | [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) | Use any [Nodemailer transport](https://nodemailer.com/transports), including SMTP, Resend, SendGrid, and more. This was provided by default in Payload 2.x. This is the easiest migration path. |
| Resend | [@payloadcms/email-resend](https://www.npmjs.com/package/@payloadcms/email-resend) | Resend email via their REST API. This is preferred for serverless platforms such as Vercel because it is much more lightweight than the nodemailer adapter. |
## Nodemailer Configuration
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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 |
## 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
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
// Nodemailer transportOptions
transportOptions: {
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
}),
})
```
**Example email options using nodemailer.createTransport:**
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailer from 'nodemailer'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
// Any Nodemailer transport can be used
transport: nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
}),
}),
})
```
**Custom Transport:**
You also have the ability to bring your own nodemailer transport. This is an example of using the SendGrid nodemailer transport.
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailerSendgrid from 'nodemailer-sendgrid'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
transportOptions: nodemailerSendgrid({
apiKey: process.env.SENDGRID_API_KEY,
}),
}),
})
```
During development, if you pass nothing to `nodemailerAdapter`, it will use the [ethereal.email](https://ethereal.email) service.
This will log the ethereal.email details to console on startup.
```ts
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter(),
})
```
## Resend Configuration
The Resend adapter requires an API key to be passed in the options. This can be found in the Resend dashboard. This is the preferred package if you are deploying on Vercel because this is much more lightweight than the Nodemailer adapter.
| Option | Description |
| ------ | ----------------------------------- |
| apiKey | The API key for the Resend service. |
```ts
import { buildConfig } from 'payload'
import { resendAdapter } from '@payloadcms/email-resend'
export default buildConfig({
email: resendAdapter({
defaultFromAddress: 'dev@payloadcms.com',
defaultFromName: 'Payload CMS',
apiKey: process.env.RESEND_API_KEY || '',
}),
})
```
## 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 `html` or `text` for the email being sent. Other options are also available and can be seen in the sendEmail args. Support for these will depend on the adapter being used.
```ts
// Example of sending an email
const email = await payload.sendEmail({
to: 'test@example.com',
subject: 'This is a test email',
text: 'This is my message body',
})
```
## 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](../hooks/overview).
# Jobs Queue
Source: https://payloadcms.com/docs/jobs-queue/overview
Payload's Jobs Queue gives you a simple, yet powerful way to offload large or future tasks to separate compute resources which is a very powerful feature of many application frameworks.
### Example use cases
**Non-blocking workloads**
You might need to perform some complex, slow-running logic in a Payload [Hook](../hooks/overview) but you don't want that hook to "block" or slow down the response returned from the Payload API. Instead of running this logic directly in a hook, which would block your API response from returning until the expensive work is completed, you can queue a new Job and let it run at a later date.
Examples:
- Create vector embeddings from your documents, and keep them in sync as your documents change
- Send data to a third-party API on document change
- Trigger emails based on customer actions
**Scheduled actions**
If you need to schedule an action to be run or processed at a certain date in the future, you can queue a job with the `waitUntil` property set. This will make it so the job is not "picked up" until that `waitUntil` date has passed.
Examples:
- Process scheduled posts, where the scheduled date is at a time set in the future
- Unpublish posts at a given time
- Send a reminder email to a customer after X days of signing up for a trial
**Periodic sync or similar scheduled action**
Some applications may need to perform a regularly scheduled operation of some type. Jobs are perfect for this because you can execute their logic using `cron`, scheduled nightly, every twelve hours, or some similar time period.
Examples:
- You'd like to send emails to all customers on a regular, scheduled basis
- Periodically trigger a rebuild of your frontend at night
- Sync resources to or from a third-party API during non-peak times
**Offloading complex operations**
You may run into the need to perform computationally expensive functions which might slow down your main Payload API server(s). The Jobs Queue allows you to offload these tasks to a separate compute resource rather than slowing down the server(s) that run your Payload APIs. With Payload Task definitions, you can even keep large dependencies out of your main Next.js bundle by dynamically importing them only when they are used. This keeps your Next.js + Payload compilation fast and ensures large dependencies do not get bundled into your Payload production build.
Examples:
- You need to create (and then keep in sync) vector embeddings of your documents as they change, but you use an open source model to generate embeddings
- You have a PDF generator that needs to dynamically build and send PDF versions of documents to customers
- You need to use a headless browser to perform some type of logic
- You need to perform a series of actions, each of which depends on a prior action and should be run in as "durable" of a fashion as possible
### How it works
There are a few concepts that you should become familiarized with before using Payload's Jobs Queue. We recommend learning what each of these does in order to fully understand how to leverage the power of Payload's Jobs Queue.
1. [Tasks](../jobs-queue/tasks)
1. [Workflows](../jobs-queue/workflows)
1. [Jobs](../jobs-queue/jobs)
1. [Queues](../jobs-queue/queues)
All of these pieces work together in order to allow you to offload long-running, expensive, or future scheduled work from your main APIs.
Here's a quick overview:
- A Task is a specific function that performs business logic
- Workflows are groupings of specific tasks which should be run in-order, and can be retried from a specific point of failure
- A Job is an instance of a single task or workflow which will be executed
- A Queue is a way to segment your jobs into different "groups" - for example, some to run nightly, and others to run every 10 minutes
### Visualizing Jobs in the Admin UI
By default, the internal `payload-jobs` collection is hidden from the Payload Admin Panel. To make this collection visible for debugging or inspection purposes, you can override its configuration using `jobsCollectionOverrides`.
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ... other config
jobs: {
// ... other job settings
jobsCollectionOverrides: ({ defaultJobsCollection }) => {
if (!defaultJobsCollection.admin) {
defaultJobsCollection.admin = {}
}
defaultJobsCollection.admin.hidden = false
return defaultJobsCollection
},
},
})
```
# Tasks
Source: https://payloadcms.com/docs/jobs-queue/tasks
A **"Task"** is a function definition that performs business logic and whose
input and output are both strongly typed.
You can register Tasks on the Payload config, and then create [Jobs](../jobs-queue/jobs) or [Workflows](../jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried.
Tasks can either be defined within the `jobs.tasks` array in your Payload config, or they can be defined inline within a workflow.
### Defining tasks in the config
Simply add a task to the `jobs.tasks` array in your Payload config. A task consists of the following fields:
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `slug` | Define a slug-based name for this job. This slug needs to be unique among both tasks and workflows. |
| `handler` | The function that should be responsible for running the job. You can either pass a string-based path to the job function file, or the job function itself. If you are using large dependencies within your job, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - Payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this task. By default, this is "Task" + the capitalized task slug. |
| `outputSchema` | Define the output field schema - Payload will generate a type for this schema. |
| `label` | Define a human-friendly label for this task. |
| `onFail` | Function to be executed if the task fails. |
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. If this is undefined, the task will either inherit the retries from the workflow or have no retries. If this is 0, the task will not be retried. By default, this is undefined. |
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks up a Job that includes this task.
It should return an object with an `output` key, which should contain the output of the task as you've defined.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
// Configure this task to automatically retry
// up to two times
retries: 2,
// This is a unique identifier for the task
slug: 'createPost',
// These are the arguments that your Task will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
// These are the properties that the function should output
outputSchema: [
{
name: 'postID',
type: 'text',
required: true,
},
],
// This is the function that is run when the task is invoked
handler: async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
},
} as TaskConfig<'createPost'>,
],
},
})
```
In addition to defining handlers as functions directly provided to your Payload config, you can also pass an _absolute path_ to where the handler is defined. If your task has large dependencies, and you are planning on executing your jobs in a separate process that has access to the filesystem, this could be a handy way to make sure that your Payload + Next.js app remains quick to compile and has minimal dependencies.
Keep in mind that this is an advanced feature that may require a sophisticated build pipeline, especially when using it in production or within Next.js, e.g. by calling opening the `/api/payload-jobs/run` endpoint. You will have to transpile the handler files separately and ensure they are available in the same location when the job is run. If you're using an endpoint to execute your jobs, it's recommended to define your handlers as functions directly in your Payload Config, or use import paths handlers outside of Next.js.
In general, this is an advanced use case. Here's how this would look:
`payload.config.ts:`
```ts
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
jobs: {
tasks: [
{
// ...
// The #createPostHandler is a named export within the `createPost.ts` file
handler:
path.resolve(dirname, 'src/tasks/createPost.ts') +
'#createPostHandler',
},
],
},
})
```
Then, the `createPost` file itself:
`src/tasks/createPost.ts:`
```ts
import type { TaskHandler } from 'payload'
export const createPostHandler: TaskHandler<'createPost'> = async ({
input,
job,
req,
}) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
}
```
### Configuring task restoration
By default, if a task has passed previously and a workflow is re-run, the task will not be re-run. Instead, the output from the previous task run will be returned. This is to prevent unnecessary re-runs of tasks that have already passed.
You can configure this behavior through the `retries.shouldRestore` property. This property accepts a boolean or a function.
If `shouldRestore` is set to true, the task will only be re-run if it previously failed. This is the default behavior.
If `shouldRestore` this is set to false, the task will be re-run even if it previously succeeded, ignoring the maximum number of retries.
If `shouldRestore` is a function, the return value of the function will determine whether the task should be re-run. This can be used for more complex restore logic, e.g you may want to re-run a task up to X amount of times and then restore it for consecutive runs, or only re-run a task if the input has changed.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
slug: 'myTask',
retries: {
shouldRestore: false,
},
// ...
} as TaskConfig<'myTask'>,
],
},
})
```
Example - determine whether a task should be restored based on the input data:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
slug: 'myTask',
inputSchema: [
{
name: 'someDate',
type: 'date',
required: true,
},
],
retries: {
shouldRestore: ({ input }) => {
if (new Date(input.someDate) > new Date()) {
return false
}
return true
},
},
// ...
} as TaskConfig<'myTask'>,
],
},
})
```
## Nested tasks
You can run sub-tasks within an existing task, by using the `tasks` or `inlineTask` arguments passed to the task `handler` function:
```ts
export default buildConfig({
// ...
jobs: {
// It is recommended to set `addParentToTaskLog` to `true` when using nested tasks, so that the parent task is included in the task log
// This allows for better observability and debugging of the task execution
addParentToTaskLog: true,
tasks: [
{
slug: 'parentTask',
inputSchema: [
{
name: 'text',
type: 'text',
},
],
handler: async ({ input, req, tasks, inlineTask }) => {
await inlineTask('Sub Task 1', {
task: () => {
// Do something
return {
output: {},
}
},
})
await tasks.CreateSimple('Sub Task 2', {
input: { message: 'hello' },
})
return {
output: {},
}
},
} as TaskConfig<'parentTask'>,
],
},
})
```
# Workflows
Source: https://payloadcms.com/docs/jobs-queue/workflows
A **"Workflow"** is an optional way to *combine multiple tasks together* in a
way that can be gracefully retried from the point of failure.
They're most helpful when you have multiple tasks in a row, and you want to configure each task to be able to be retried if they fail.
If a task within a workflow fails, the Workflow will automatically "pick back up" on the task where it failed and **not re-execute any prior tasks that have already been executed**.
#### Defining a workflow
The most important aspect of a Workflow is the `handler`, where you can declare when and how the tasks should run by simply calling the `runTask` function. If any task within the workflow, fails, the entire `handler` function will re-run.
However, importantly, tasks that have successfully been completed will simply re-return the cached and saved output without running again. The Workflow will pick back up where it failed and only task from the failure point onward will be re-executed.
To define a JS-based workflow, simply add a workflow to the `jobs.workflows` array in your Payload config. A workflow consists of the following fields:
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `slug` | Define a slug-based name for this workflow. This slug needs to be unique among both tasks and workflows. |
| `handler` | The function that should be responsible for running the workflow. You can either pass a string-based path to the workflow function file, or workflow job function itself. If you are using large dependencies within your workflow, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - Payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this workflow. By default, this is "Workflow" + the capitalized workflow slug. |
| `label` | Define a human-friendly label for this workflow. |
| `queue` | Optionally, define the queue name that this workflow should be tied to. Defaults to "default". |
| `retries` | You can define `retries` on the workflow level, which will enforce that the workflow can only fail up to that number of retries. If a task does not have retries specified, it will inherit the retry count as specified on the workflow. You can specify `0` as `workflow` retries, which will disregard all `task` retry specifications and fail the entire workflow on any task failure. You can leave `workflow` retries as undefined, in which case, the workflow will respect what each task dictates as their own retry count. By default this is undefined, meaning workflows retries are defined by their tasks |
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
// The arguments that the workflow will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
// The handler that defines the "control flow" of the workflow
// Notice how it uses the `tasks` argument to execute your predefined tasks.
// These are strongly typed!
handler: async ({ job, tasks }) => {
// This workflow first runs a task called `createPost`.
// You need to define a unique ID for this task invocation
// that will always be the same if this workflow fails
// and is re-executed in the future. Here, we hard-code it to '1'
const output = await tasks.createPost('1', {
input: {
title: job.input.title,
},
})
// Once the prior task completes, it will run a task
// called `updatePost`
await tasks.updatePost('2', {
input: {
post: job.taskStatus.createPost['1'].output.postID, // or output.postID
title: job.input.title + '2',
},
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```
#### Running tasks inline
In the above example, our workflow was executing tasks that we already had defined in our Payload config. But, you can also run tasks without predefining them.
To do this, you can use the `inlineTask` function.
The drawbacks of this approach are that tasks cannot be re-used across workflows as easily, and the **task data stored in the job** will not be typed. In the following example, the inline task data will be stored on the job under `job.taskStatus.inline['2']` but completely untyped, as types for dynamic tasks like these cannot be generated beforehand.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
handler: async ({ job, tasks, inlineTask }) => {
// Here, we run a predefined task.
// The `createPost` handler arguments and return type
// are both strongly typed
const output = await tasks.createPost('1', {
input: {
title: job.input.title,
},
})
// Here, this task is not defined in the Payload config
// and is "inline". Its output will be stored on the Job in the database
// however its arguments will be untyped.
const { newPost } = await inlineTask('2', {
task: async ({ req }) => {
const newPost = await req.payload.update({
collection: 'post',
id: '2',
req,
retries: 3,
data: {
title: 'updated!',
},
})
return {
output: {
newPost
},
}
},
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```
# Jobs
Source: https://payloadcms.com/docs/jobs-queue/jobs
Now that we have covered Tasks and Workflows, we can tie them together with a concept called a Job.
Whereas you define Workflows and Tasks, which control your business logic, a
**Job** is an individual instance of either a Task or a Workflow which
contains many tasks.
For example, let's say we have a Workflow or Task that describes the logic to sync information from Payload to a third-party system. This is how you'd declare how to sync that info, but it wouldn't do anything on its own. In order to run that task or workflow, you'd create a Job that references the corresponding Task or Workflow.
Jobs are stored in the Payload database in the `payload-jobs` collection, and you can decide to keep a running list of all jobs, or configure Payload to delete the job when it has been successfully executed.
#### Queuing a new job
In order to queue a job, you can use the `payload.jobs.queue` function.
Here's how you'd queue a new Job, which will run a `createPostAndUpdate` workflow:
```ts
const createdJob = await payload.jobs.queue({
// Pass the name of the workflow
workflow: 'createPostAndUpdate',
// The input type will be automatically typed
// according to the input you've defined for this workflow
input: {
title: 'my title',
},
})
```
In addition to being able to queue new Jobs based on Workflows, you can also queue a job for a single Task:
```ts
const createdJob = await payload.jobs.queue({
task: 'createPost',
input: {
title: 'my title',
},
})
```
#### Cancelling Jobs
Payload allows you to cancel jobs that are either queued or currently running. When cancelling a running job, the current task will finish executing, but no subsequent tasks will run. This happens because the job checks its cancellation status between tasks.
##### Cancel a Single Job
To cancel a specific job, use the `payload.jobs.cancelByID` method with the job's ID:
```ts
await payload.jobs.cancelByID({
id: createdJob.id,
})
```
##### Cancel Multiple Jobs
To cancel multiple jobs at once, use the `payload.jobs.cancel` method with a `Where` query:
```ts
await payload.jobs.cancel({
where: {
workflowSlug: {
equals: 'createPost',
},
},
})
```
# Queues
Source: https://payloadcms.com/docs/jobs-queue/queues
Queues are the final aspect of Payload's Jobs Queue and deal with how to _run your jobs_. Up to this point, all we've covered is how to queue up jobs to run, but so far, we aren't actually running any jobs.
A **Queue** is a grouping of jobs that should be executed in order of when
they were added.
When you go to run jobs, Payload will query for any jobs that are added to the queue and then run them. By default, all queued jobs are added to the `default` queue.
**But, imagine if you wanted to have some jobs that run nightly, and other jobs which should run every five minutes.**
By specifying the `queue` name when you queue a new job using `payload.jobs.queue()`, you can queue certain jobs with `queue: 'nightly'`, and other jobs can be left as the default queue.
Then, you could configure two different runner strategies:
1. A `cron` that runs nightly, querying for jobs added to the `nightly` queue
2. Another that runs any jobs that were added to the `default` queue every ~5 minutes or so
## Executing jobs
As mentioned above, you can queue jobs, but the jobs won't run unless a worker picks up your jobs and runs them. This can be done in four ways:
### Cron jobs
The `jobs.autoRun` property allows you to configure cron jobs that automatically run queued jobs at specified intervals. Note that this does not _queue_ new jobs - only _runs_ jobs that are already in the specified queue.
**Example**:
```ts
export default buildConfig({
// Other configurations...
jobs: {
tasks: [
// your tasks here
],
// autoRun can optionally be a function that receives `payload` as an argument
autoRun: [
{
cron: '0 * * * *', // every hour at minute 0
limit: 100, // limit jobs to process each run
queue: 'hourly', // name of the queue
},
// add as many cron jobs as you want
],
shouldAutoRun: async (payload) => {
// Tell Payload if it should run jobs or not. This function is optional and will return true by default.
// This function will be invoked each time Payload goes to pick up and run jobs.
// If this function ever returns false, the cron schedule will be stopped.
return true
},
},
})
```
autoRun is intended for use with a dedicated server that is always running,
and should not be used on serverless platforms like Vercel.
### Endpoint
You can execute jobs by making a fetch request to the `/api/payload-jobs/run` endpoint:
```ts
// Here, we're saying we want to run only 100 jobs for this invocation
// and we want to pull jobs from the `nightly` queue:
await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
})
```
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
**Query Parameters:**
- `limit`: The maximum number of jobs to run in this invocation (default: 10).
- `queue`: The name of the queue to run jobs from. If not specified, jobs will be run from the `default` queue.
- `allQueues`: If set to `true`, all jobs from all queues will be run. This will ignore the `queue` parameter.
**Vercel Cron Example**
If you're deploying on Vercel, you can add a `vercel.json` file in the root of your project that configures Vercel Cron to invoke the `run` endpoint on a cron schedule.
Here's an example of what this file will look like:
```json
{
"crons": [
{
"path": "/api/payload-jobs/run",
"schedule": "*/5 * * * *"
}
]
}
```
The configuration above schedules the endpoint `/api/payload-jobs/run` to be invoked every 5 minutes.
The last step will be to secure your `run` endpoint so that only the proper users can invoke the runner.
To do this, you can set an environment variable on your Vercel project called `CRON_SECRET`, which should be a random string—ideally 16 characters or longer.
Then, you can modify the `access` function for running jobs by ensuring that only Vercel can invoke your runner.
```ts
export default buildConfig({
// Other configurations...
jobs: {
access: {
run: ({ req }: { req: PayloadRequest }): boolean => {
// Allow logged in users to execute this endpoint (default)
if (req.user) return true
// If there is no logged in user, then check
// for the Vercel Cron secret to be present as an
// Authorization header:
const authHeader = req.headers.get('authorization')
return authHeader === `Bearer ${process.env.CRON_SECRET}`
},
},
// Other job configurations...
},
})
```
This works because Vercel automatically makes the `CRON_SECRET` environment variable available to the endpoint as the `Authorization` header when triggered by the Vercel Cron, ensuring that the jobs can be run securely.
After the project is deployed to Vercel, the Vercel Cron job will automatically trigger the `/api/payload-jobs/run` endpoint in the specified schedule, running the queued jobs in the background.
### Local API
If you want to process jobs programmatically from your server-side code, you can use the Local API:
**Run all jobs:**
```ts
// Run all jobs from the `default` queue - default limit is 10
const results = await payload.jobs.run()
// You can customize the queue name and limit by passing them as arguments:
await payload.jobs.run({ queue: 'nightly', limit: 100 })
// Run all jobs from all queues:
await payload.jobs.run({ allQueues: true })
// You can provide a where clause to filter the jobs that should be run:
await payload.jobs.run({
where: { 'input.message': { equals: 'secret' } },
})
```
**Run a single job:**
```ts
const results = await payload.jobs.runByID({
id: myJobID,
})
```
### Bin script
Finally, you can process jobs via the bin script that comes with Payload out of the box. By default, this script will run jobs from the `default` queue, with a limit of 10 jobs per invocation:
```sh
pnpm payload jobs:run
```
You can override the default queue and limit by passing the `--queue` and `--limit` flags:
```sh
pnpm payload jobs:run --queue myQueue --limit 15
```
If you want to run all jobs from all queues, you can pass the `--all-queues` flag:
```sh
pnpm payload jobs:run --all-queues
```
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
```sh
pnpm payload jobs:run --cron "*/5 * * * *"
```
You can also pass `--handle-schedules` flag to the `jobs:run` command to make it schedule jobs according to configured schedules:
```sh
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
```
## Processing Order
By default, jobs are processed first in, first out (FIFO). This means that the first job added to the queue will be the first one processed. However, you can also configure the order in which jobs are processed.
### Jobs Configuration
You can configure the order in which jobs are processed in the jobs configuration by passing the `processingOrder` property. This mimics the Payload [sort](../queries/sort) property that's used for functionality such as `payload.find()`.
```ts
export default buildConfig({
// Other configurations...
jobs: {
tasks: [
// your tasks here
],
processingOrder: '-createdAt', // Process jobs in reverse order of creation = LIFO
},
})
```
You can also set this on a queue-by-queue basis:
```ts
export default buildConfig({
// Other configurations...
jobs: {
tasks: [
// your tasks here
],
processingOrder: {
default: 'createdAt', // FIFO
queues: {
nightly: '-createdAt', // LIFO
myQueue: '-createdAt', // LIFO
},
},
},
})
```
If you need even more control over the processing order, you can pass a function that returns the processing order - this function will be called every time a queue starts processing jobs.
```ts
export default buildConfig({
// Other configurations...
jobs: {
tasks: [
// your tasks here
],
processingOrder: ({ queue }) => {
if (queue === 'myQueue') {
return '-createdAt' // LIFO
}
return 'createdAt' // FIFO
},
},
})
```
### Local API
You can configure the order in which jobs are processed in the `payload.jobs.queue` method by passing the `processingOrder` property.
```ts
const createdJob = await payload.jobs.queue({
workflow: 'createPostAndUpdate',
input: {
title: 'my title',
},
processingOrder: '-createdAt', // Process jobs in reverse order of creation = LIFO
})
```
# Job Schedules
Source: https://payloadcms.com/docs/jobs-queue/schedules
Payload's `schedule` property lets you enqueue Jobs regularly according to a cron schedule - daily, weekly, hourly, or any custom interval. This is ideal for tasks or workflows that must repeat automatically and without manual intervention.
Scheduling Jobs differs significantly from running them:
- **Queueing**: Scheduling only creates (enqueues) the Job according to your cron expression. It does not immediately execute any business logic.
- **Running**: Execution happens separately through your Jobs runner - such as autorun, or manual invocation using `payload.jobs.run()` or the `payload-jobs/run` endpoint.
Use the `schedule` property specifically when you have recurring tasks or workflows. To enqueue a single Job to run once in the future, use the `waitUntil` property instead.
## Example use cases
**Regular emails or notifications**
Send nightly digests, weekly newsletters, or hourly updates.
**Batch processing during off-hours**
Process analytics data or rebuild static sites during low-traffic times.
**Periodic data synchronization**
Regularly push or pull updates to or from external APIs.
## Handling schedules
Something needs to actually trigger the scheduling of jobs (execute the scheduling lifecycle seen below). By default, the `jobs.autorun` configuration, as well as the `/api/payload-jobs/run` will also handle scheduling for the queue specified in the `autorun` configuration.
You can disable this behavior by setting `disableScheduling: true` in your `autorun` configuration, or by passing `disableScheduling=true` to the `/api/payload-jobs/run` endpoint. This is useful if you want to handle scheduling manually, for example, by using a cron job or a serverless function that calls the `/api/payload-jobs/handle-schedules` endpoint or the `payload.jobs.handleSchedules()` local API method.
### Bin Scripts
Payload provides a set of bin scripts that can be used to handle schedules. If you're already using the `jobs:run` bin script, you can set it to also handle schedules by passing the `--handle-schedules` flag:
```sh
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
```
If you only want to handle schedules, you can use the dedicated `jobs:handle-schedules` bin script:
```sh
pnpm payload jobs:handle-schedules --cron "*/5 * * * *" --queue myQueue # or --all-queues
```
## Defining schedules on Tasks or Workflows
Schedules are defined using the `schedule` property:
```ts
export type ScheduleConfig = {
cron: string // required, supports seconds precision
queue: string // required, the queue to push Jobs onto
hooks?: {
// Optional hooks to customize scheduling behavior
beforeSchedule?: BeforeScheduleFn
afterSchedule?: AfterScheduleFn
}
}
```
### Example schedule
The following example demonstrates scheduling a Job to enqueue every day at midnight:
```ts
import type { TaskConfig } from 'payload'
export const SendDigestEmail: TaskConfig<'SendDigestEmail'> = {
slug: 'SendDigestEmail',
schedule: [
{
cron: '0 0 * * *', // Every day at midnight
queue: 'nightly',
},
],
handler: async () => {
await sendDigestToAllUsers()
},
}
```
This configuration only queues the Job - it does not execute it immediately. To actually run the queued Job, you configure autorun in your Payload config (note that autorun should **not** be used on serverless platforms):
```ts
export default buildConfig({
jobs: {
autoRun: [
{
cron: '* * * * *', // Runs every minute
queue: 'nightly',
},
],
tasks: [SendDigestEmail],
},
})
```
That way, Payload's scheduler will automatically enqueue the job into the `nightly` queue every day at midnight. The autorun configuration will check the `nightly` queue every minute and execute any Jobs that are due to run.
## Scheduling lifecycle
Here's how the scheduling process operates in detail:
1. **Cron evaluation**: Payload (or your external trigger in `manual` mode) identifies which schedules are due to run. To do that, it will
read the `payload-jobs-stats` global which contains information about the last time each scheduled task or workflow was run.
2. **BeforeSchedule hook**:
- The default beforeSchedule hook checks how many active or runnable jobs of the same type that have been queued by the scheduling system currently exist.
If such a job exists, it will skip scheduling a new one.
- You can provide your own `beforeSchedule` hook to customize this behavior. For example, you might want to allow multiple overlapping Jobs or dynamically set the Job input data.
3. **Enqueue Job**: Payload queues up a new job. This job will have `waitUntil` set to the next scheduled time based on the cron expression.
4. **AfterSchedule hook**:
- The default afterSchedule hook updates the `payload-jobs-stats` global metadata with the last scheduled time for the Job.
- You can provide your own afterSchedule hook to it for custom logging, metrics, or other post-scheduling actions.
## Customizing concurrency and input (Advanced)
You may want more control over concurrency or dynamically set Job inputs at scheduling time. For instance, allowing multiple overlapping Jobs to be scheduled, even if a previously scheduled job has not completed yet, or preparing dynamic data to pass to your Job handler:
```ts
import { countRunnableOrActiveJobsForQueue } from 'payload'
schedule: [
{
cron: '* * * * *', // every minute
queue: 'reports',
hooks: {
beforeSchedule: async ({ queueable, req }) => {
const runnableOrActiveJobsForQueue =
await countRunnableOrActiveJobsForQueue({
queue: queueable.scheduleConfig.queue,
req,
taskSlug: queueable.taskConfig?.slug,
workflowSlug: queueable.workflowConfig?.slug,
onlyScheduled: true,
})
// Allow up to 3 simultaneous scheduled jobs and set dynamic input
return {
shouldSchedule: runnableOrActiveJobsForQueue < 3,
input: { text: 'Hi there' },
}
},
},
},
]
```
This allows fine-grained control over how many Jobs can run simultaneously and provides dynamically computed input values each time a Job is scheduled.
## Scheduling in serverless environments
On serverless platforms, scheduling must be triggered externally since Payload does not automatically run cron schedules in ephemeral environments. You have two main ways to trigger scheduling manually:
- **Invoke via Payload's API:** `payload.jobs.handleSchedules()`
- **Use the REST API endpoint:** `/api/payload-jobs/handle-schedules`
- **Use the run endpoint, which also handles scheduling by default:** `GET /api/payload-jobs/run`
For example, on Vercel, you can set up a Vercel Cron to regularly trigger scheduling:
- **Vercel Cron Job:** Configure Vercel Cron to periodically call `GET /api/payload-jobs/handle-schedules`. If you would like to auto-run your scheduled jobs as well, you can use the `GET /api/payload-jobs/run` endpoint.
Once Jobs are queued, their execution depends entirely on your configured runner setup (e.g., autorun, or manual invocation).
# Query Presets
Source: https://payloadcms.com/docs/query-presets/overview
Query Presets allow you to save and share filters, columns, and sort orders for your [Collections](../configuration/collections). This is useful for reusing common or complex filtering patterns and/or sharing them across your team.
Each Query Preset is saved as a new record in the database under the `payload-query-presets` collection. This allows for an endless number of preset configurations, where the users of your app define the presets that are most useful to them, rather than being hard coded into the Payload Config.
Within the [Admin Panel](../admin/overview), Query Presets are applied to the List View. When enabled, new controls are displayed for users to manage presets. Once saved, these presets can be loaded up at any time and optionally shared with others.
To enable Query Presets on a Collection, use the `enableQueryPresets` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
// highlight-start
enableQueryPresets: true,
// highlight-end
}
```
## Config Options
While not required, you may want to customize the behavior of Query Presets to suit your needs, such as add custom labels or access control rules.
Settings for Query Presets are managed on the `queryPresets` property at the root of your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
// highlight-start
queryPresets: {
// ...
},
// highlight-end
})
```
The following options are available for Query Presets:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `access` | Used to define custom collection-level access control that applies to all presets. [More details](#access-control). |
| `filterConstraints` | Used to define which constraints are available to users when managing presets. [More details](#constraint-access-control). |
| `constraints` | Used to define custom document-level access control that apply to individual presets. [More details](#document-access-control). |
| `labels` | Custom labels to use for the Query Presets collection. |
## Access Control
Query Presets are subject to the same [Access Control](../access-control/overview) as the rest of Payload. This means you can use the same patterns you are already familiar with to control who can read, update, and delete presets.
Access Control for Query Presets can be customized in two ways:
1. [Collection Access Control](#collection-access-control): Applies to all presets. These rules are not controllable by the user and are statically defined in the config.
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are dynamically defined on each record in the database.
### Collection Access Control
Collection-level access control applies to _all_ presets within the Query Presets collection. Users cannot control these rules, they are written statically in your config.
To add Collection Access Control, use the `queryPresets.access` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
// highlight-start
access: {
read: ({ req: { user } }) =>
user ? user?.roles?.some((role) => role === 'admin') : false,
update: ({ req: { user } }) =>
user ? user?.roles?.some((role) => role === 'admin') : false,
},
// highlight-end
},
})
```
This example restricts all Query Presets to users with the role of `admin`.
**Note:** Custom access control will override the defaults on this collection,
including the requirement for a user to be authenticated. Be sure to include
any necessary checks in your custom rules unless you intend on making these
publicly accessible.
### Document Access Control
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each record.
When a user manages a preset, document-level access control options will be available to them in the Admin Panel for each operation.
By default, Payload provides a set of sensible defaults for all Query Presets, but you can customize these rules to suit your needs:
- **Only Me**: Only the user who created the preset can read, update, and delete it.
- **Everyone**: All users can read, update, and delete the preset.
- **Specific Users**: Only select users can read, update, and delete the preset.
#### Custom Access Control
You can augment the default access control rules with your own custom rules. This can be useful for creating more complex access control patterns that the defaults don't provide, such as for RBAC.
Adding custom access control rules requires:
1. A label to display in the dropdown
2. A set of fields to conditionally render when that option is selected
3. A function that returns the access control rules for that option
To do this, use the `queryPresets.constraints` property in your [Payload Config](../configuration/overview).
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
// highlight-start
constraints: {
read: [
{
label: 'Specific Roles',
value: 'specificRoles',
fields: [
{
name: 'roles',
type: 'select',
hasMany: true,
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
],
},
],
access: ({ req: { user } }) => ({
'access.read.roles': {
in: [user?.roles],
},
}),
},
],
},
// highlight-end
},
})
```
In this example, we've added a new option called `Specific Roles` that allows users to select from a list of roles. When this option is selected, the user will be prompted to select one or more roles from a list of options. The access control rule for this option is that the user operating on the preset must have one of the selected roles.
**Note:** Payload places your custom fields into the `access[operation]` field
group, so your rules will need to reflect this.
The following options are available for each constraint:
| Option | Description |
| -------- | ------------------------------------------------------------------------ |
| `label` | The label to display in the dropdown for this constraint. |
| `value` | The value to store in the database when this constraint is selected. |
| `fields` | An array of fields to render when this constraint is selected. |
| `access` | A function that determines the access control rules for this constraint. |
### Constraint Access Control
Used to dynamically filter which constraints are available based on the current user, document data, or other criteria.
Some examples of this might include:
- Ensuring that only "admins" are allowed to make a preset available to "everyone"
- Preventing the "onlyMe" option from being selected based on a hypothetical "disablePrivatePresets" checkbox
When a user lacks the permission to set a constraint, the option will either be hidden from them, or disabled if it is already saved to that preset.
To do this, you can use the `filterConstraints` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
// highlight-start
filterConstraints: ({ req, options }) =>
!req.user?.roles?.includes('admin')
? options.filter(
(option) =>
(typeof option === 'string' ? option : option.value) !==
'everyone',
)
: options,
// highlight-end
},
})
```
The `filterConstraints` function receives the same arguments as [`filterOptions`](../fields/select#filteroptions) in the [Select field](../fields/select).
# Trash
Source: https://payloadcms.com/docs/trash/overview
Trash (also known as soft delete) allows documents to be marked as deleted without being permanently removed. When enabled on a collection, deleted documents will receive a `deletedAt` timestamp, making it possible to restore them later, view them in a dedicated Trash view, or permanently delete them.
Soft delete is a safer way to manage content lifecycle, giving editors a chance to review and recover documents that may have been deleted by mistake.
**Note:** The Trash feature is currently in beta and may be subject to change
in minor version updates.
## Collection Configuration
To enable soft deleting for a collection, set the `trash` property to `true`:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
trash: true,
fields: [
{
name: 'title',
type: 'text',
},
// other fields...
],
}
```
When enabled, Payload automatically injects a deletedAt field into the collection's schema. This timestamp is set when a document is soft-deleted, and cleared when the document is restored.
## Admin Panel behavior
Once `trash` is enabled, the Admin Panel provides a dedicated Trash view for each collection:
- A new route is added at `/collections/:collectionSlug/trash`
- The `Trash` view shows all documents that have a `deletedAt` timestamp
From the Trash view, you can:
- Use bulk actions to manage trashed documents:
- **Restore** to clear the `deletedAt` timestamp and return documents to their original state
- **Delete** to permanently remove selected documents
- **Empty Trash** to select and permanently delete all trashed documents at once
- Enter each document's **edit view**, just like in the main list view. While in the edit view of a trashed document:
- All fields are in a **read-only** state
- Standard document actions (e.g., Save, Publish, Restore Version) are hidden and disabled.
- The available actions are **Restore** and **Permanently Delete**.
- Access to the **API**, **Versions**, and **Preview** views is preserved.
When deleting a document from the main collection List View, Payload will soft-delete the document by default. A checkbox in the delete confirmation modal allows users to skip the trash and permanently delete instead.
## API Support
Soft deletes are fully supported across all Payload APIs: **Local**, **REST**, and **GraphQL**.
The following operations respect and support the `trash` functionality:
- `find`
- `findByID`
- `update`
- `updateByID`
- `delete`
- `deleteByID`
- `findVersions`
- `findVersionByID`
### Understanding `trash` Behavior
Passing `trash: true` to these operations will **include soft-deleted documents** in the query results.
To return _only_ soft-deleted documents, you must combine `trash: true` with a `where` clause that checks if `deletedAt` exists.
### Examples
#### Local API
Return all documents including trashed:
```ts
const result = await payload.find({
collection: 'posts',
trash: true,
})
```
Return only trashed documents:
```ts
const result = await payload.find({
collection: 'posts',
trash: true,
where: {
deletedAt: {
exists: true,
},
},
})
```
Return only non-trashed documents:
```ts
const result = await payload.find({
collection: 'posts',
trash: false,
})
```
#### REST
Return **all** documents including trashed:
```http
GET /api/posts?trash=true
```
Return **only trashed** documents:
```http
GET /api/posts?trash=true&where[deletedAt][exists]=true
```
Return only non-trashed documents:
```http
GET /api/posts?trash=false
```
#### GraphQL
Return all documents including trashed:
```ts
query {
Posts(trash: true) {
docs {
id
deletedAt
}
}
}
```
Return only trashed documents:
```ts
query {
Posts(
trash: true
where: { deletedAt: { exists: true } }
) {
docs {
id
deletedAt
}
}
}
```
Return only non-trashed documents:
```ts
query {
Posts(trash: false) {
docs {
id
deletedAt
}
}
}
```
## Access Control
All trash-related actions (delete, permanent delete) respect the `delete` access control defined in your collection config.
This means:
- If a user is denied delete access, they cannot soft delete or permanently delete documents
## Versions and Trash
When a document is soft-deleted:
- It can no longer have a version **restored** until it is first restored from trash
- Attempting to restore a version while the document is in trash will result in an error
- This ensures consistency between the current document state and its version history
However, versions are still fully **visible and accessible** from the **edit view** of a trashed document. You can view the full version history, but must restore the document itself before restoring any individual version.
# Troubleshooting
Source: https://payloadcms.com/docs/troubleshooting/troubleshooting
## Dependency mismatches
All `payload` and `@payloadcms/*` packages must be on exactly the same version and installed only once.
When two copies—or two different versions—of any of these packages (or of `react` / `react-dom`) appear in your dependency graph, you can see puzzling runtime errors. The most frequent is a broken React context:
```bash
TypeError: Cannot destructure property 'config' of...
```
This happens because one package imports a hook (most commonly `useConfig`) from _version A_ while the context provider comes from _version B_. The fix is always the same: make sure every Payload-related and React package resolves to the same module.
### Confirm whether duplicates exist
The first thing to do is to confirm whether duplicative dependencies do in fact exist.
There are two ways to do this:
1. Using pnpm's built-in inspection tool
```bash
pnpm why @payloadcms/ui
```
This prints the dependency tree and shows which versions are being installed. If you see more than one distinct version—or the same version listed under different paths—you have duplication.
2. Manual check (works with any package manager)
```bash
find node_modules -name package.json \
-exec grep -H '"name": "@payloadcms/ui"' {} \;
```
Most of these hits are likely symlinks created by pnpm. Edit the matching package.json files (temporarily add a comment or change a description) to confirm whether they point to the same physical folder or to multiple copies.
Perform the same two checks for react and react-dom; a second copy of React can cause identical symptoms.
#### If no duplicates are found
`@payloadcms/ui` intentionally contains two bundles of itself, so you may see dual paths even when everything is correct. Inside the Payload Admin UI you must import only:
- `@payloadcms/ui`
- `@payloadcms/ui/rsc`
- `@payloadcms/ui/shared`
Any other deep import such as `@payloadcms/ui/elements/Button` should **only** be used in your own frontend, outside of the Payload Admin Panel. Those deep entries are published un-bundled to help you tree-shake and ship a smaller client bundle if you only need a few components from `@payloadcms/ui`.
### Fixing depedendency issues
These steps assume `pnpm`, which the Payload team recommends and uses internally. The principles apply to other package managers like npm and yarn as well. Do note that yarn 1.x is not supported by Payload.
1. Pin every critical package to an exact version
In package.json remove `^` or `~` from all versions of:
- `payload`
- `@payloadcms/*`
- `react`
- `react-dom`
Prefixes allow your package manager to float to a newer minor/patch release, causing mismatches.
2. Delete node_modules
Old packages often linger even after you change versions or removed them from your package.json. Deleting node_modules ensures a clean slate.
3. Re-install dependencies
```bash
pnpm install
```
#### If the error persists
1. Clean the global store (pnpm only)
```bash
pnpm store prune
```
2. Delete the lockfile
Depending on your package manager, this could be `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`.
Make sure you delete the lockfile **and** the node_modules folder at the same time, then run `pnpm install`. This forces a fresh, consistent resolution for all packages. It will also update all packages with dynamic versions to the latest version.
While it's best practice to manage dependencies in such a way where the lockfile can easily be re-generated (often this is the easiest way to resolve dependency issues), this may break your project if you have not tested the latest versions of your dependencies.
If you are using a version control system, make sure to commit your lockfile after this step.
3. Deduplicate anything that slipped through
```bash
pnpm dedupe
```
**Still stuck?**
- Switch to `pnpm` if you are on npm. Its symlinked store helps reducing accidental duplication.
- Inspect the lockfile directly for peer-dependency violations.
- Check project-level .npmrc / .pnpmfile.cjs overrides.
- Run [Syncpack](https://www.npmjs.com/package/syncpack) to enforce identical versions of every `@payloadcms/*`, `react`, and `react-dom` reference.
Absolute last resort: add Webpack aliases so that all imports of a given package resolve to the same path (e.g. `resolve.alias['react'] = path.resolve('./node_modules/react')`). Keep this only until you can fix the underlying version skew.
### Monorepos
Another error you might see is the following or similarly related to hooks, in particular when `next` versions are mismatched:
```bash
useUploadHandlers must be used within UploadHandlersProvider
```
This is a common pitfall when using a monorepo setup with multiple packages. In this case, ensure that all packages in the monorepo use the same version of `payload`, `@payloadcms/*`, `next`, `react`, and `react-dom`. You can use pnpm with workspaces to manage dependencies across packages in a monorepo effectively. Unfortunately this error becomes harder to debug inside a monorepo due to how package managers hoist dependencies as well as resolve them.
If you've pinned the versions and the error persists we recommend removing `.next/`, `node_modules/` and if possible deleting the lockfile and re-generating it to ensure that all packages in the monorepo are using the same version of the dependencies mentioned above.
## "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 check 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.
## Using --experimental-https
If you are using the `--experimental-https` flag when starting your Payload server, you may run into issues with your WebSocket connection for HMR (Hot Module Reloading) in development mode.
To resolve this, you can set the `USE_HTTPS` environment variable to `true` in your `.env` file:
```
USE_HTTPS=true
```
This will ensure that the WebSocket connection uses the correct protocol (`wss://` instead of `ws://`) when HTTPS is enabled.
Alternatively if more of your URL is dynamic, you can set the full URL for the WebSocket connection using the `PAYLOAD_HMR_URL_OVERRIDE` environment variable:
```
PAYLOAD_HMR_URL_OVERRIDE=wss://localhost:3000/_next/webpack-hmr
```
# TypeScript - Overview
Source: https://payloadcms.com/docs/typescript/overview
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 custom functionality like [Plugins](../plugins/overview), [Hooks](../hooks/overview), [Access Control](../access-control/overview) functions, [Custom Views](../custom-components/custom-views), [GraphQL queries / mutations](../graphql/overview) or anything else.
## Config Types
- [Base config](../configuration/overview#typescript)
- [Collections](../configuration/collections#typescript)
- [Globals](../configuration/globals#typescript)
- [Fields](../fields/overview#typescript)
## Hook Types
- [Collection hooks](../hooks/collections#typescript)
- [Global hooks](../hooks/globals#typescript)
- [Field hooks](../hooks/fields#typescript)
# Generating TypeScript Interfaces
Source: https://payloadcms.com/docs/typescript/generating-types
While building your own custom functionality into Payload, like [Plugins](../plugins/overview), [Hooks](../hooks/overview), [Access Control](../access-control/overview) functions, [Custom Views](../custom-components/custom-views), [GraphQL queries / mutations](../graphql/overview), 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`.
## Custom generated types
Payload generates your types based on a JSON schema. You can extend that JSON schema, and thus the generated types, by passing a function to `typescript.schema`:
```ts
// payload.config.ts
{
// ...
typescript: {
schema: [
({ jsonSchema }) => {
// Modify the JSON schema here
jsonSchema.definitions.Test = {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string' },
},
required: ['title', 'content'],
}
return jsonSchema
},
]
}
}
// This will generate the following type in your payload-types.ts:
export interface Test {
title: string
content: string
[k: string]: unknown
}
```
This function takes the existing JSON schema as an argument and returns the modified JSON schema. It can be useful for plugins that wish to generate their own types.
## Example Usage
For example, let's look at the following simple Payload Config:
```ts
import type { Config } from 'payload'
const config: Config = {
serverURL: process.env.NEXT_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
}
```
**Naming Collisions**
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.
## Using your types
Now that your types have been generated, Payload's 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
**Important**
Payload needs to be able to find your config to generate your types.
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 `pnpm generate:types` to easily generate your types.
# Plugins
Source: https://payloadcms.com/docs/plugins/overview
Payload Plugins take full advantage of the modularity of the [Payload Config](../configuration/overview), allowing developers to easily inject custom—sometimes complex—functionality into Payload apps from a very small touch-point. This is especially useful for sharing your work across multiple projects or with the greater Payload community.
There are many [Official Plugins](#official-plugins) available that solve for some of the most common uses cases, such as the [Form Builder Plugin](./form-builder) or [SEO Plugin](./seo). There are also [Community Plugins](#community-plugins) available, maintained entirely by contributing members. To extend Payload's functionality in some other way, you can easily [build your own plugin](./build-your-own).
To configure Plugins, use the `plugins` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
// highlight-start
plugins: [
// Add Plugins here
],
// highlight-end
})
```
Writing Plugins is no more complex than writing regular JavaScript. If you know the basic concept of [callback functions](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) or how [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works, and are up to speed with Payload concepts, then writing a plugin will be a breeze.
Because we rely on a simple config-based structure, Payload Plugins simply
take in an existing config and returns a _modified_ config with new fields,
hooks, collections, admin views, or anything else you can think of.
**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
## Official Plugins
Payload maintains a set of Official Plugins that solve for some of the common use cases. These plugins are maintained by the Payload team and its contributors and are guaranteed to be stable and up-to-date.
- [Form Builder](./form-builder)
- [Nested Docs](./nested-docs)
- [Redirects](./redirects)
- [Search](./search)
- [Sentry](./sentry)
- [SEO](./seo)
- [Stripe](./stripe)
- [Import/Export](./import-export)
- [Ecommerce](../ecommerce/overview)
You can also [build your own plugin](./build-your-own) to easily extend Payload's functionality in some other way. Once your plugin is ready, consider [sharing it with the community](#community-plugins).
Plugins are changing every day, so be sure to check back often to see what new plugins may have been added. If you have a specific plugin you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions).
For a complete list of Official Plugins, visit the [Packages
Directory](https://github.com/payloadcms/payload/tree/main/packages) of the
[Payload Monorepo](https://github.com/payloadcms/payload).
## Community Plugins
Community Plugins are those that are maintained entirely by outside contributors. They are a great way to share your work across the ecosystem for others to use. You can discover Community Plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
Some plugins have become so widely used that they are adopted as an [Official Plugin](#official-plugins), such as the [Lexical Plugin](https://github.com/AlessioGr/payload-plugin-lexical). If you have a plugin that you think should be an Official Plugin, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions).
For maintainers building plugins for others to use, please add the
`payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin)
to help others find it.
## Example
The base [Payload Config](../configuration/overview) allows for a `plugins` property which takes an `array` of [Plugin Configs](./build-your-own).
```ts
import { buildConfig } from 'payload'
import { addLastModified } from './addLastModified.ts'
const config = buildConfig({
// ...
// highlight-start
plugins: [addLastModified],
// highlight-end
})
```
Payload Plugins are executed _after_ the incoming config is validated, but
before it is sanitized and has had default options merged in. After all
plugins are executed, the full config with all plugins will be sanitized.
Here is an example what the `addLastModified` plugin from above might look like. It adds a `lastModifiedBy` field to all Payload collections. For full details, see [how to build your own plugin](./build-your-own).
```ts
import { Config, Plugin } from 'payload'
export 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
}
```
**Reminder:** See [how to build your own plugin](./build-your-own) for a more
in-depth explication on how create your own Payload Plugin.
# Building Your Own Plugin
Source: https://payloadcms.com/docs/plugins/build-your-own
Building your own [Payload Plugin](./overview) is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the [Plugin Template](#plugin-template) to get up and running quickly.
To use the template, run `npx create-payload-app@latest --template plugin`
directly in your terminal.
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'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](../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/tree/main/templates/plugin), 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
### The root folder
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.
### The dev folder
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're starting from scratch, you can easily setup a dev environment like this:
```
mkdir dev
cd dev
npx create-payload-app@latest
```
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config.ts`.
```
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.ts` and build out the dev project as needed to test your plugin.
When you're ready to start development, navigate into this folder with `cd dev`
And then start the project with `pnpm 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're all set!
```
let payload: 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 => {
payload.logger.info('Seeding data...')
await payload.create({
collection: 'new-collection',
data: {
title: 'Seeded title',
},
})
// Add additional seed data here
}
```
## Building a Plugin
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
```
import type { Config } from 'payload'
export const samplePlugin =
(pluginOptions: PluginTypes) =>
(incomingConfig: Config): Config => {
// create copy of incoming config
let config = { ...incomingConfig }
/**
* This is where you could modify the
* config based on the plugin options
*/
// If you wanted to add a new collection:
config.collections = [
...(config.collections || []),
newCollection,
]
// If you wanted to add a new global:
config.globals = [
...(config.globals || []),
newGlobal,
]
/**
* If you wanted to add a new field to a collection:
*
* 1. Loop over collections
* 2. Find the collection you want to add the field to
* 3. Add the field to the collection
*/
// If you wanted to add to the onInit:
config.onInit = async payload => {
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
// Add additional onInit code here
}
// Finally, return the modified config
return config
}
```
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
### 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'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'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 array and object like properties such as admin, globals and hooks:
```
config.globals = [
...(config.globals || []),
// Add additional globals here
]
config.hooks = {
...(config.hooks || {}),
// Add additional hooks here
}
```
### Extending functions
Function properties cannot use spread syntax. The way to extend them is to execute the existing function if it exists and then run your additional functionality.
Here is an example extending 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)
}
```
## 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.
### Include tests in your GitHub CI workflow
If you'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/use-cases-and-examples/building-and-testing/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 (SemVer)
With the [Semantic Versioning](https://semver.org/) (SemVer) system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.
# Form Builder Plugin
Source: https://payloadcms.com/docs/plugins/form-builder

This plugin allows you to build and manage custom forms directly within the [Admin Panel](../admin/overview). 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.
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.
## 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 [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-form-builder
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
],
plugins: [
formBuilderPlugin({
// 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](../fields/blocks#block-configs) _keyed to the block's slug_. See [Fields](#fields) for more details.
```ts
// payload.config.ts
formBuilderPlugin({
// ...
fields: {
text: true,
textarea: true,
select: true,
radio: true,
email: true,
state: true,
country: true,
checkbox: true,
number: true,
message: true,
date: false,
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
formBuilderPlugin({
// ...
redirectRelationships: ['pages'],
})
```
### `beforeEmail`
The `beforeEmail` property is a [beforeChange](../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
formBuilderPlugin({
// ...
beforeEmail: (emailsToSend, beforeChangeParams) => {
// 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?)
}))
},
})
```
For full types with `beforeChangeParams`, you can import the types from the plugin:
```ts
import type { BeforeEmail } from '@payloadcms/plugin-form-builder'
// Your generated FormSubmission type
import type { FormSubmission } from '@payload-types'
// Pass it through and 'data' or 'originalDoc' will now be typed
const beforeEmail: BeforeEmail = (
emailsToSend,
beforeChangeParams,
) => {
// 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?)
}))
}
```
### `defaultToEmail`
Provide a fallback for the email address to send form submissions to. If the email in form configuration does not have a to email set, this email address will be used. If this is not provided then it falls back to the `defaultFromAddress` in your [email configuration](../email/overview).
```ts
// payload.config.ts
formBuilderPlugin({
// ...
defaultToEmail: 'test@example.com',
})
```
### `formOverrides`
Override anything on the `forms` collection by sending a [Payload Collection Config](../configuration/collections) to the `formOverrides` property.
Note that the `fields` property is a function that receives the default fields and returns an array of fields. This is because the `fields` property is a special case that is merged with the default fields, rather than replacing them. This allows you to map over default fields and modify them as needed.
Good to know: The form collection is publicly available to read by default.
The emails field is locked for authenticated users only. If you have any
frontend users you should override the access permissions for both the
collection and the emails field to make sure you don't leak out any private
emails.
```ts
// payload.config.ts
formBuilderPlugin({
// ...
formOverrides: {
slug: 'contact-forms',
access: {
read: ({ req: { user } }) => !!user, // authenticated users only
update: () => false,
},
fields: ({ defaultFields }) => {
return [
...defaultFields,
{
name: 'custom',
type: 'text',
},
]
},
},
})
```
### `formSubmissionOverrides`
Override anything on the `form-submissions` collection by sending a [Payload Collection Config](../configuration/collections) to the `formSubmissionOverrides` property.
By default, this plugin relies on [Payload access
control](../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 once
it has been created, or read a submission unless they have permission. You can
override this behavior or any other property as needed.
```ts
// payload.config.ts
formBuilderPlugin({
// ...
formSubmissionOverrides: {
slug: 'leads',
fields: ({ defaultFields }) => {
return [
...defaultFields,
{
name: 'custom',
type: 'text',
},
]
},
},
})
```
### `handlePayment`
The `handlePayment` property is a [beforeChange](../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
formBuilderPlugin({
// ...
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](../fields/blocks) _keyed to the block's slug_. See [Field Overrides](#field-overrides) for more details on how to do this.
**Note:** "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`.
### 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. |
| `placeholder` | string | The placeholder text for 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 that define the select options. See below for more details. |
#### Select Options
Each option in the `options` array defines a selectable choice for the select field.
| Property | Type | Description |
| -------- | ------ | ----------------------------------- |
| `label` | string | The display text for the option. |
| `value` | string | The value submitted for the option. |
### Radio
Maps to radio button inputs on your front-end. Used to allow users to select a single option from a list of choices.
| 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 that define the radio options. See below for more details. |
#### Radio Options
Each option in the `options` array defines a selectable choice for the radio field.
| Property | Type | Description |
| -------- | ------ | ----------------------------------- |
| `label` | string | The display text for the option. |
| `value` | string | The value submitted for the option. |
### 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. |
### Date
Maps to a `date` input on your front-end. Used to collect a date value.
| Property | Type | Description |
| -------------- | -------- | ---------------------------------------------------- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | date | 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` | 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. |
### 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](../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
formBuilderPlugin({
// ...
fields: {
text: {
...fields.text,
labels: {
singular: 'Custom Text Field',
plural: 'Custom Text Fields',
},
},
},
})
```
### Customizing the date field default value
You can custommise the default value of the date field and any other aspects of the date block in this way.
Note that the end submission source will be responsible for the timezone of the date. Payload only stores the date in UTC format.
```ts
import { fields as formFields } from '@payloadcms/plugin-form-builder'
// payload.config.ts
formBuilderPlugin({
fields: {
// date: true, // just enable it without any customizations
date: {
...formFields.date,
fields: [
...(formFields.date && 'fields' in formFields.date
? formFields.date.fields.map((field) => {
if ('name' in field && field.name === 'defaultValue') {
return {
...field,
timezone: true, // optionally enable timezone
admin: {
...field.admin,
description: 'This is a date field',
},
}
}
return field
})
: []),
],
},
},
})
```
### Preventing generated schema naming conflicts
Plugin fields can cause GraphQL type name collisions with your own blocks or collections. This results in errors like:
```plaintext
Error: Schema must contain uniquely named types but contains multiple types named "Country"
```
You can resolve this by overriding:
- `graphQL.singularName` in your collection config (for GraphQL schema conflicts)
- `interfaceName` in your block config
- `interfaceName` in the plugin field config
```ts
// payload.config.ts
formBuilderPlugin({
fields: {
country: {
interfaceName: 'CountryFormBlock', // overrides the generated type name to avoid a conflict
},
},
})
```
## Email
This plugin relies on the [email configuration](../email/overview) defined in your Payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
### Email formatting
The email contents supports rich text which will be serialized to HTML on the server before being sent. By default it reads the global configuration of your rich text editor.
The email subject and body supports inserting dynamic fields from the form submission data using the `{{field_name}}` syntax. For example, if you have a field called `name` in your form, you can include this in the email body like so:
```html
Thank you for your submission, {{name}}!
```
You can also use `{{*}}` as a wildcard to output all the data in a key:value format and `{{*:table}}` to output all the data in a table format.
## TypeScript
All types can be directly imported:
```ts
import type {
PluginConfig,
Form,
FormSubmission,
FieldsConfig,
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






# Import Export Plugin
Source: https://payloadcms.com/docs/plugins/import-export

**Note**: This plugin is in **beta** as some aspects of it may change on any
minor releases. It is under development and currently only supports exporting
of collection data.
This plugin adds features that give admin users the ability to download or create export data as an upload collection and import it back into a project.
## Core Features
- Export data as CSV or JSON format via the admin UI
- Download the export directly through the browser
- Create a file upload of the export data
- Use the jobs queue for large exports
- (Coming soon) Import collection data
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-import-export
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
import { importExportPlugin } from '@payloadcms/plugin-import-export'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
importExportPlugin({
collections: ['users', 'pages'],
// see below for a list of available options
}),
],
})
export default config
```
## Options
| Property | Type | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `collections` | string[] | Collections to include Import/Export controls in. Defaults to all collections. |
| `debug` | boolean | If true, enables debug logging. |
| `disableDownload` | boolean | If true, disables the download button in the export preview UI. |
| `disableJobsQueue` | boolean | If true, forces the export to run synchronously. |
| `disableSave` | boolean | If true, disables the save button in the export preview UI. |
| `format` | string | Forces a specific export format (`csv` or `json`), hides the format dropdown, and prevents the user from choosing the export format. |
| `overrideExportCollection` | function | Function to override the default export collection; takes the default export collection and allows you to modify and return it. |
## Field Options
In addition to the above plugin configuration options, you can granularly set the following field level options using the `custom['plugin-import-export']` properties in any of your collections.
| Property | Type | Description |
| ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `disabled` | boolean | When `true` the field is completely excluded from the import-export plugin. |
| `toCSV` | function | Custom function used to modify the outgoing csv data by manipulating the data, siblingData or by returning the desired value. |
### Customizing the output of CSV data
To manipulate the data that a field exports you can add `toCSV` custom functions. This allows you to modify the outgoing csv data by manipulating the data, siblingData or by returning the desired value.
The toCSV function argument is an object with the following properties:
| Property | Type | Description |
| ------------ | ------- | ----------------------------------------------------------------- |
| `columnName` | string | The CSV column name given to the field. |
| `doc` | object | The top level document |
| `row` | object | The object data that can be manipulated to assign data to the CSV |
| `siblingDoc` | object | The document data at the level where it belongs |
| `value` | unknown | The data for the field. |
Example function:
```ts
const pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'author',
type: 'relationship',
relationTo: 'users',
custom: {
'plugin-import-export': {
toCSV: ({ value, columnName, row }) => {
// add both `author_id` and the `author_email` to the csv export
if (
value &&
typeof value === 'object' &&
'id' in value &&
'email' in value
) {
row[`${columnName}_id`] = (value as { id: number | string }).id
row[`${columnName}_email`] = (value as { email: string }).email
}
},
},
},
},
],
}
```
## Exporting Data
There are four possible ways that the plugin allows for exporting documents, the first two are available in the admin UI from the list view of a collection:
1. Direct download - Using a `POST` to `/api/exports/download` and streams the response as a file download
2. File storage - Goes to the `exports` collection as an uploads enabled collection
3. Local API - A create call to the uploads collection: `payload.create({ slug: 'uploads', ...parameters })`
4. Jobs Queue - `payload.jobs.queue({ task: 'createCollectionExport', input: parameters })`
By default, a user can use the Export drawer to create a file download by choosing `Save` or stream a downloadable file directly without persisting it by using the `Download` button. Either option can be disabled to provide the export experience you desire for your use-case.
The UI for creating exports provides options so that users can be selective about which documents to include and also which columns or fields to include.
It is necessary to add access control to the uploads collection configuration using the `overrideExportCollection` function if you have enabled this plugin on collections with data that some authenticated users should not have access to.
**Note**: Users who have read access to the upload collection may be able to
download data that is normally not readable due to [access
control](../access-control/overview).
The following parameters are used by the export function to handle requests:
| Property | Type | Description |
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `format` | text | Either `csv` or `json` to determine the shape of data exported |
| `limit` | number | The max number of documents to return |
| `sort` | select | The field to use for ordering documents |
| `locale` | string | The locale code to query documents or `all` |
| `draft` | string | Either `yes` or `no` to return documents with their newest drafts for drafts enabled collections |
| `fields` | string[] | Which collection fields are used to create the export, defaults to all |
| `collectionSlug` | string | The slug to query against |
| `where` | object | The WhereObject used to query documents to export. This is set by making selections or filters from the list view |
| `filename` | text | What to call the export being created |
# Multi-Tenant Plugin
Source: https://payloadcms.com/docs/plugins/multi-tenant

This plugin sets up multi-tenancy for your application from within your [Admin Panel](../admin/overview). It does so by adding a `tenant` field to all specified collections. Your front-end application can then query data by tenant. You must add the Tenants collection so you control what fields are available for each tenant.
This plugin is completely open-source and the [source code can be found
here](https://github.com/payloadcms/payload/tree/main/packages/plugin-multi-tenant).
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/choose) with as much
detail as possible.
## Core features
- Adds a `tenant` field to each specified collection
- Adds a tenant selector to the admin panel, allowing you to switch between tenants
- Filters list view results by selected tenant
- Filters relationship fields by selected tenant
- Ability to create "global" like collections, 1 doc per tenant
- Automatically assign a tenant to new documents
**Warning**
By default this plugin cleans up documents when a tenant is deleted. You should ensure you have
strong access control on your tenants collection to prevent deletions by unauthorized users.
You can disable this behavior by setting `cleanupAfterTenantDelete` to `false` in the plugin options.
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-multi-tenant
```
### Options
The plugin accepts an object with the following properties:
```ts
type MultiTenantPluginConfig = {
/**
* Base path for your application
*
* https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath
*
* @default undefined
*/
basePath?: string
/**
* After a tenant is deleted, the plugin will attempt to clean up related documents
* - removing documents with the tenant ID
* - removing the tenant from users
*
* @default true
*/
cleanupAfterTenantDelete?: boolean
/**
* Automatically
*/
collections: {
[key in CollectionSlug]?: {
/**
* Override the access result from the collection access control functions
*
* The function receives:
* - accessResult: the original result from the access control function
* - accessKey: 'read', 'create', 'update', 'delete', 'readVersions', or 'unlock'
* - ...restOfAccessArgs: the original arguments passed to the access control function
*/
accessResultOverride?: CollectionAccessResultOverride
/**
* Opt out of adding the tenant field and place
* it manually using the `tenantField` export from the plugin
*/
customTenantField?: boolean
/**
* Set to `true` if you want the collection to behave as a global
*
* @default false
*/
isGlobal?: boolean
/**
* Overrides for the tenant field, will override the entire tenantField configuration
*/
tenantFieldOverrides?: CollectionTenantFieldConfigOverrides
/**
* Set to `false` if you want to manually apply the baseListFilter
* Set to `false` if you want to manually apply the baseFilter
*
* @default true
*/
useBaseFilter?: boolean
/**
* @deprecated Use `useBaseFilter` instead. If both are defined,
* `useBaseFilter` will take precedence. This property remains only
* for backward compatibility and may be removed in a future version.
*
* Originally, `baseListFilter` was intended to filter only the List View
* in the admin panel. However, base filtering is often required in other areas
* such as internal link relationships in the Lexical editor.
*
* @default true
*/
useBaseListFilter?: boolean
/**
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
*
* @default true
*/
useTenantAccess?: boolean
}
}
/**
* Enables debug mode
* - Makes the tenant field visible in the admin UI within applicable collections
*
* @default false
*/
debug?: boolean
/**
* Enables the multi-tenant plugin
*
* @default true
*/
enabled?: boolean
/**
* Localization for the plugin
*/
i18n?: {
translations: {
[key in AcceptedLanguages]?: {
/**
* Shown inside 3 dot menu on edit document view
*
* @default 'Assign Tenant'
*/
'assign-tenant-button-label'?: string
/**
* Shown as the title of the assign tenant modal
*
* @default 'Assign "{{title}}"'
*/
'assign-tenant-modal-title'?: string
/**
* Shown as the label for the assigned tenant field in the assign tenant modal
*
* @default 'Assigned Tenant'
*/
'field-assignedTenant-label'?: string
/**
* Shown as the label for the global tenant selector in the admin UI
*
* @default 'Filter by Tenant'
*/
'nav-tenantSelector-label'?: string
}
}
}
/**
* Field configuration for the field added to all tenant enabled collections
*/
tenantField?: RootTenantFieldConfigOverrides
/**
* Field configuration for the field added to the users collection
*
* If `includeDefaultField` is `false`, you must include the field on your users collection manually
* This is useful if you want to customize the field or place the field in a specific location
*/
tenantsArrayField?:
| {
/**
* Access configuration for the array field
*/
arrayFieldAccess?: ArrayField['access']
/**
* Name of the array field
*
* @default 'tenants'
*/
arrayFieldName?: string
/**
* Name of the tenant field
*
* @default 'tenant'
*/
arrayTenantFieldName?: string
/**
* When `includeDefaultField` is `true`, the field will be added to the users collection automatically
*/
includeDefaultField?: true
/**
* Additional fields to include on the tenants array field
*/
rowFields?: Field[]
/**
* Access configuration for the tenant field
*/
tenantFieldAccess?: RelationshipField['access']
}
| {
arrayFieldAccess?: never
arrayFieldName?: string
arrayTenantFieldName?: string
/**
* When `includeDefaultField` is `false`, you must include the field on your users collection manually
*/
includeDefaultField?: false
rowFields?: never
tenantFieldAccess?: never
}
/**
* Customize tenant selector label
*
* Either a string or an object where the keys are i18n codes and the values are the string labels
*
* @deprecated Use `i18n.translations` instead.
*/
tenantSelectorLabel?:
| Partial<{
[key in AcceptedLanguages]?: string
}>
| string
/**
* The slug for the tenant collection
*
* @default 'tenants'
*/
tenantsSlug?: string
/**
* Function that determines if a user has access to _all_ tenants
*
* Useful for super-admin type users
*/
userHasAccessToAllTenants?: (
user: ConfigTypes extends { user: unknown }
? ConfigTypes['user']
: TypedUser,
) => boolean
/**
* Override the access result on the users collection access control functions
*
* The function receives:
* - accessResult: the original result from the access control function
* - accessKey: 'read', 'create', 'update', 'delete', 'readVersions', or 'unlock'
* - ...restOfAccessArgs: the original arguments passed to the access control function
*/
usersAccessResultOverride?: CollectionAccessResultOverride
/**
* Opt out of adding access constraints to the tenants collection
*/
useTenantsCollectionAccess?: boolean
/**
* Opt out including the baseListFilter to filter tenants by selected tenant
*/
useTenantsListFilter?: boolean
/**
* Opt out including the baseListFilter to filter users by selected tenant
*/
useUsersTenantFilter?: boolean
}
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
import type { Config } from './payload-types'
const config = buildConfig({
collections: [
{
slug: 'tenants',
admin: {
useAsTitle: 'name',
},
fields: [
// remember, you own these fields
// these are merely suggestions/examples
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
},
{
name: 'domain',
type: 'text',
required: true,
},
],
},
],
plugins: [
multiTenantPlugin({
collections: {
pages: {},
navigation: {
isGlobal: true,
},
},
}),
],
})
export default config
```
## Front end usage
The plugin scaffolds out everything you will need to separate data by tenant. You can use the `tenant` field to filter data from enabled collections in your front-end application.
In your frontend you can query and constrain data by tenant with the following:
```tsx
const pagesBySlug = await payload.find({
collection: 'pages',
depth: 1,
draft: false,
limit: 1000,
overrideAccess: false,
where: {
// your constraint would depend on the
// fields you added to the tenants collection
// here we are assuming a slug field exists
// on the tenant collection, like in the example above
'tenant.slug': {
equals: 'gold',
},
},
})
```
### NextJS rewrites
Using NextJS rewrites and this route structure `/[tenantDomain]/[slug]`, we can rewrite routes specifically for domains requested:
```ts
async rewrites() {
return [
{
source: '/((?!admin|api)):path*',
destination: '/:tenantDomain/:path*',
has: [
{
type: 'host',
value: '(?.*)',
},
],
},
];
}
```
### React Hooks
Below are the hooks exported from the plugin that you can import into your own custom components to consume.
#### useTenantSelection
You can import this like so:
```tsx
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
...
const tenantContext = useTenantSelection()
```
The hook returns the following context:
```ts
type ContextType = {
/**
* Array of options to select from
*/
options: OptionObject[]
/**
* The currently selected tenant ID
*/
selectedTenantID: number | string | undefined
/**
* Prevents a refresh when the tenant is changed
*
* If not switching tenants while viewing a "global",
* set to true
*/
setPreventRefreshOnChange: React.Dispatch>
/**
* Sets the selected tenant ID
*
* @param args.id - The ID of the tenant to select
* @param args.refresh - Whether to refresh the page
* after changing the tenant
*/
setTenant: (args: {
id: number | string | undefined
refresh?: boolean
}) => void
}
```
## Examples
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) also contains an official [Multi-Tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant) example.
# Nested Docs Plugin
Source: https://payloadcms.com/docs/plugins/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.
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.
## 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 [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-nested-docs
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin
with [options](#options):
```ts
import { buildConfig } from 'payload'
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'slug',
type: 'text',
},
],
},
],
plugins: [
nestedDocsPlugin({
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
nestedDocsPlugin({
//...
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 |
| `collection` | `Object` | The collection config of the current document |
#### `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
nestedDocsPlugin({
//...
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 |
| `collection` | `Object` | The collection config of the current document |
#### `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.
**Note:**
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`.
## 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 type { CollectionConfig } from 'payload'
import { createParentField } from '@payloadcms/plugin-nested-docs'
import { createBreadcrumbsField } from '@payloadcms/plugin-nested-docs'
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',
},
),
],
}
```
**Note:**
If overriding the `name` of either `breadcrumbs` or `parent` fields, you must specify the
`breadcrumbsFieldSlug` or `parentFieldSlug` respectively.
## 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](../configuration/localization) docs.
## TypeScript
All types can be directly imported:
```ts
import {
PluginConfig,
GenerateURL,
GenerateLabel,
} from '@payloadcms/plugin-nested-docs/types'
```
## Examples
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.
# Redirects Plugin
Source: https://payloadcms.com/docs/plugins/redirects

This plugin allows you to easily manage redirects for your application from within your [Admin Panel](../admin/overview). 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.
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%redirects&template=bug_report.md&title=plugin-redirects%3A)
with as much detail as possible.
## 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 [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-redirects
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
import { redirectsPlugin } from '@payloadcms/plugin-redirects'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
],
plugins: [
redirectsPlugin({
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. |
| `redirectTypes` | `string[]` | Provide an array of redirects if you want to provide options for the type of redirects to be supported. |
| `redirectTypeFieldOverride` | `Field` | A partial Field config that allows you to override the Redirect Type field if enabled above. |
Note that the fields in overrides take a function that receives the default fields and returns an array of fields. This allows you to add fields to the collection.
```ts
redirectsPlugin({
collections: ['pages'],
overrides: {
fields: ({ defaultFields }) => {
return [
...defaultFields,
{
type: 'text',
name: 'customField',
},
]
},
},
redirectTypes: ['301', '302'],
redirectTypeFieldOverride: {
label: 'Redirect Type (Overridden)',
},
})
```
## TypeScript
All types can be directly imported:
```ts
import { PluginConfig } from '@payloadcms/plugin-redirects/types'
```
## Examples
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.
# Search Plugin
Source: https://payloadcms.com/docs/plugins/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.
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.
## 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
- Allows you to reindex search results by collection on demand
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-search
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```js
import { buildConfig } from 'payload'
import { searchPlugin } from '@payloadcms/plugin-search'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
{
slug: 'posts',
fields: [],
},
],
plugins: [
searchPlugin({
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.
#### `localize`
By default, the search plugin will add `localization: true` to the `title` field of the newly added `search` collection if you have localization enabled. If you would like to disable this behavior, you can set this to `false`.
#### `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](../configuration/collections) and merges it in with the default `search` collection config provided by the plugin.
Note that the `fields` property is a function that receives an object with a `defaultFields` key. This is an array of fields that are automatically added to the `search` collection. You can modify this array or add new fields to it.
```ts
// payload.config.ts
{
// ...
searchPlugin({
searchOverrides: {
slug: 'search-results',
fields: ({ defaultFields }) => [
...defaultFields,
{
name: 'excerpt',
type: 'textarea',
admin: {
position: 'sidebar',
},
},
],
},
}),
}
```
#### `beforeSync`
Before creating or updating a search record, the `beforeSync` function runs. This is an [afterChange](../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
// - You also need to add the `excerpt` field in the `searchOverrides` config
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](../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](../versions/drafts) enabled for this to apply.
#### `reindexBatchSize`
A number that, when specified, will be used as the value to determine how many search documents to fetch for reindexing at a time in each batch. If not set, this will default to `50`.
### Collection reindexing
Collection reindexing allows you to recreate search documents from your search-enabled collections on demand. This is useful if you have existing documents that don't already have search indexes, commonly when adding `plugin-search` to an existing project. To get started, navigate to your search collection and click the pill in the top right actions slot of the list view labelled `Reindex`. This will open a popup with options to select one of your search-enabled collections, or all, for reindexing.
## TypeScript
All types can be directly imported:
```ts
import type { SearchConfig, BeforeSync } from '@payloadcms/plugin-search/types'
```
# Sentry Plugin
Source: https://payloadcms.com/docs/plugins/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.
Sentry does smart stuff with error data to make bugs easier to find and fix. -
[sentry.io](https://sentry.io/)
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
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-sentry%3A)
with as much detail as possible.
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-sentry
```
## Sentry for Next.js setup
This plugin requires to complete the [Sentry + Next.js setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/) before.
You can use either the [automatic setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/#install) with the installation wizard:
```sh
npx @sentry/wizard@latest -i nextjs
```
Or the [Manual Setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/)
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin and pass in your Sentry DSN as an option.
```ts
import { buildConfig } from 'payload'
import { sentryPlugin } from '@payloadcms/plugin-sentry'
import { Pages, Media } from './collections'
import * as Sentry from '@sentry/nextjs'
const config = buildConfig({
collections: [Pages, Media],
plugins: [sentryPlugin({ Sentry })],
})
export default config
```
## Instrumenting Database Queries
If you want Sentry to capture Postgres query performance traces, you need to inject the Sentry-patched `pg` driver into the Postgres adapter. This ensures Sentry’s instrumentation hooks into your database calls.
```ts
import * as Sentry from '@sentry/nextjs'
import { buildConfig } from 'payload'
import { sentryPlugin } from '@payloadcms/plugin-sentry'
import { postgresAdapter } from '@payloadcms/db-postgres'
import pg from 'pg'
export default buildConfig({
db: postgresAdapter({
pool: { connectionString: process.env.DATABASE_URL },
pg, // Inject the patched pg driver for Sentry instrumentation
}),
plugins: [sentryPlugin({ Sentry })],
})
```
## Options
- `Sentry` : Sentry | **required**
The `Sentry` instance
Make sure to complete the [Sentry for Next.js Setup](#sentry-for-nextjs-setup)
before.
- `enabled`: boolean | optional
Set to false to disable the plugin. Defaults to `true`.
- `context`: `(args: ContextArgs) => Partial | Promise>`
Pass additional [contextual data](https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly) to Sentry
- `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.
### Example
Configure any of these options by passing them to the plugin:
```ts
import { buildConfig } from 'payload'
import { sentryPlugin } from '@payloadcms/plugin-sentry'
import * as Sentry from '@sentry/nextjs'
import { Pages, Media } from './collections'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
sentryPlugin({
options: {
captureErrors: [400, 403],
context: ({ defaultContext, req }) => {
return {
...defaultContext,
tags: {
locale: req.locale,
},
}
},
debug: true,
},
Sentry,
}),
],
})
export default config
```
## TypeScript
All types can be directly imported:
```ts
import { PluginOptions } from '@payloadcms/plugin-sentry'
```
# SEO Plugin
Source: https://payloadcms.com/docs/plugins/seo

This plugin allows you to easily manage SEO metadata for your application from within your [Admin Panel](../admin/overview). When enabled on your [Collections](../configuration/collections) and [Globals](../configuration/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 `` 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 the 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.
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.
## 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 [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-seo
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload';
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}`,
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`
A function that takes in the default fields via an object and expects an array of fields in return. You can use this to modify existing fields or add new ones.
```ts
// payload.config.ts
{
// ...
seoPlugin({
fields: ({ defaultFields }) => [
...defaultFields,
{
name: 'customField',
type: 'text',
},
],
})
}
```
##### `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](../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`.
Note that the order of plugins or fields in your config may affect whether or not the plugin can smartly merge tabs with your existing fields. If you have a complex structure we recommend you [make use of the fields directly](#direct-use-of-fields) instead of relying on this config option.
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.
##### `generateTitle`
A function that allows you to return any meta title, including from the document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateTitle: ({ doc }) => `Website.com — ${doc?.title}`,
})
}
```
All "generate" functions receive the following arguments:
| Argument | Description |
| -------------------------- | --------------------------------------------------------------------- |
| **`collectionConfig`** | The configuration of the collection. |
| **`collectionSlug`** | The slug of the collection. |
| **`doc`** | The data of the current document. |
| **`docPermissions`** | The permissions of the document. |
| **`globalConfig`** | The configuration of the global. |
| **`globalSlug`** | The slug of the global. |
| **`hasPublishPermission`** | Whether the user has permission to publish the document. |
| **`hasSavePermission`** | Whether the user has permission to save the document. |
| **`id`** | The ID of the document. |
| **`initialData`** | The initial data of the document. |
| **`initialState`** | The initial state of the document. |
| **`locale`** | The locale of the document. |
| **`preferencesKey`** | The preferences key of the document. |
| **`publishedDoc`** | The published document. |
| **`req`** | The Payload request object containing `user`, `payload`, `i18n`, etc. |
| **`title`** | The title of the document. |
| **`versionsCount`** | The number of versions of the document. |
##### `generateDescription`
A function that allows you to return any meta description, including from the document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateDescription: ({ doc }) => doc?.excerpt,
})
}
```
For a full list of arguments, see the [`generateTitle`](#generatetitle) function.
##### `generateImage`
A function that allows you to return any meta image, including from the document's content.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateImage: ({ doc }) => doc?.featuredImage,
})
}
```
For a full list of arguments, see the [`generateTitle`](#generatetitle) function.
##### `generateURL`
A function called by the search preview component to display the actual URL of your page.
```ts
// payload.config.ts
{
// ...
seoPlugin({
generateURL: ({ doc, collectionSlug }) =>
`https://yoursite.com/${collectionSlug}/${doc?.slug}`,
})
}
```
For a full list of arguments, see the [`generateTitle`](#generatetitle) function.
#### `interfaceName`
Rename the meta group interface name that is generated for TypeScript and GraphQL.
```ts
// payload.config.ts
{
// ...
seoPlugin({
interfaceName: 'customInterfaceNameSEO',
})
}
```
## Direct use of fields
There is the option to directly import any of the fields from the plugin so that you can include them anywhere as needed.
You will still need to configure the plugin in the Payload Config in order to
configure the generation functions. Since these fields are imported and used
directly, they don't have access to the plugin config so they may need
additional arguments to work the same way.
```ts
import {
MetaDescriptionField,
MetaImageField,
MetaTitleField,
OverviewField,
PreviewField,
} from '@payloadcms/plugin-seo/fields'
// Used as fields
MetaImageField({
// the upload collection slug
relationTo: 'media',
// if the `generateImage` function is configured
hasGenerateFn: true,
})
MetaDescriptionField({
// if the `generateDescription` function is configured
hasGenerateFn: true,
})
MetaTitleField({
// if the `generateTitle` function is configured
hasGenerateFn: true,
})
PreviewField({
// if the `generateUrl` function is configured
hasGenerateFn: true,
// field paths to match the target field for data
titlePath: 'meta.title',
descriptionPath: 'meta.description',
})
OverviewField({
// field paths to match the target field for data
titlePath: 'meta.title',
descriptionPath: 'meta.description',
imagePath: 'meta.image',
})
```
Tip: You can override the length rules by changing the minLength and maxLength
props on the fields. In the case of the OverviewField you can use
`titleOverrides` and `descriptionOverrides` to override the length rules.
## TypeScript
All types can be directly imported:
```ts
import type {
PluginConfig,
GenerateTitle,
GenerateDescription
GenerateURL
} from '@payloadcms/plugin-seo/types';
```
You can then pass the collections from your generated Payload types into the generation types, for example:
```ts
import type { Page } from './payload-types.ts'
import type { GenerateTitle } from '@payloadcms/plugin-seo/types'
const generateTitle: GenerateTitle = async ({ doc, locale }) => {
return `Website.com — ${doc?.title}`
}
```
## 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/ecommere) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
## Screenshots

# Stripe Plugin
Source: https://payloadcms.com/docs/plugins/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](../access-control/overview). 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.
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.
## Core features
- Hides your Stripe credentials when shipping SaaS applications
- Allows restricted keys through [Payload access control](../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 [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-stripe
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
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 or 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](../access-control/overview) and returns the result. See the [REST Proxy](#stripe-rest-proxy) section for more details. |
| `/api/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](../access-control/overview) and returns the result. This flag should only be used for local development, see the security note below for more information.
```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',
},
],
}),
})
```
If you need to proxy the API server-side, use the [stripeProxy](#node) function.
**Note:**
The `/api` part of these routes may be different based on the settings defined in your Payload
config.
**Warning:**
Opening the REST proxy endpoint in production is a potential security risk. Authenticated users will have open access to the Stripe REST API. In production, open your own endpoint and use the [stripeProxy](#node) function to proxy the Stripe API server-side.
## 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/api/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/api/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'
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.
**Note:**
If you wish to enable a _two-way_ sync, be sure to setup [`webhooks`](#webhooks) and pass the
`stripeWebhooksEndpointSecret` through your config.
```ts
import { buildConfig } from 'payload'
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
```
**Note:**
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.
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';
```
# Ecommerce Overview
Source: https://payloadcms.com/docs/ecommerce/overview

This plugin is currently in Beta and may have breaking changes in future
releases.
Payload provides an Ecommerce Plugin that allows you to add ecommerce functionality to your app. It comes a set of utilities and collections to manage products, orders, and payments. It also integrates with popular payment gateways like Stripe to handle transactions.
This plugin is completely open-source and the [source code can be found
here](https://github.com/payloadcms/payload/tree/main/packages/plugin-ecommerce).
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%redirects&template=bug_report.md&title=plugin-ecommerce%3A)
with as much detail as possible.
## Core features
The plugin ships with a wide range of features to help you get started with ecommerce:
- Products with Variants are supported by default
- Carts are tracked in Payload
- Orders and Transactions
- Addresses linked to your Customers
- Payments adapter pattern to create your own integrations (Stripe currently supported)
- Multiple currencies are supported
- React UI utilities to help you manage your frontend logic
_Currently_ the plugin does not handle shipping, taxes or subscriptions natively, but you can implement these features yourself using the provided collections and hooks.
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-ecommerce
```
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with:
```ts
import { buildConfig } from 'payload'
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
],
plugins: [
ecommercePlugin({
// You must add your access control functions here
access: {
adminOnly,
adminOnlyFieldAccess,
adminOrCustomerOwner,
adminOrPublishedStatus,
customerOnlyFieldAccess,
},
customers: { slug: 'users' },
}),
],
})
export default config
```
## Concepts
It's important to understand overall how the plugin works and the relationships between the different collections.
**Customers**
Can be any collection of users in your application. You can then limit access control only to customers depending on individual fields such
as roles on the customer collection or by collection slug if you've opted to keep them separate. Customers are linked to Carts and Orders.
**Products and Variants**
Products are the items you are selling and they will contain a price and optionally variants via a join field as well as allowed Variant Types.
Each Variant Type can contain a set of Variant Options. For example, a T-Shirt product can have a Variant Type of Size with options Small, Medium, and Large and each Variant can therefore have those options assigned to it.
**Carts**
Carts are linked to Customers or they're left entirely public for guests users and can contain multiple Products and Variants. Carts are stored in the database and can be retrieved at any time. Carts are automatically created for Customers when they add a product to their cart for the first time.
**Transactions and Orders**
Transactions are created when a payment is initiated. They contain the payment status and are linked to a Cart and Customer. Orders are created when a Transaction is successful and contain the final details of the purchase including the items, total, and customer information.
**Addresses**
Addresses are linked to Customers and can be used for billing and shipping information. They can be reused across multiple Orders.
**Payments**
The plugin uses an adapter pattern to allow for different payment gateways. The default adapter is for Stripe, but you can create your own by implementing the `PaymentAdapter` interface.
**Currencies**
The plugin supports using multiple currencies at the configuration level. Each currency will create a separate price field on the Product and Variants collections.
The package can also be used piece-meal if you only want to re-use certain parts of it, such as just the creation of Products and Variants. See [Advanced uses and examples](./advanced) for more details.
## TypeScript
The plugin will inherit the types from your generated Payload types where possible. We also export the following types:
- `Cart` - The cart type as stored in the React state and local storage and on the client side.
- `CollectionOverride` - Type for overriding collections.
- `CurrenciesConfig` - Type for the currencies configuration.
- `EcommercePluginConfig` - The configuration object for the ecommerce plugin.
- `FieldsOverride` - Type for overriding fields in collections.
All types can be directly imported:
```ts
import { EcommercePluginConfig } from '@payloadcms/plugin-ecommerce/types'
```
## Template
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce), which uses this plugin.
# Ecommerce Plugin
Source: https://payloadcms.com/docs/ecommerce/plugin
## Basic Usage
In the `plugins` array of your [Payload Config](../configuration/overview), call the plugin with:
```ts
import { buildConfig } from 'payload'
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
const config = buildConfig({
collections: [
{
slug: 'pages',
fields: [],
},
],
plugins: [
ecommercePlugin({
// You must add your access control functions here
access: {
adminOnly,
adminOnlyFieldAccess,
adminOrCustomerOwner,
adminOrPublishedStatus,
customerOnlyFieldAccess,
},
customers: { slug: 'users' },
}),
],
})
export default config
```
## Options
| Option | Type | Description |
| -------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `access` | `object` | Configuration to override the default access control, use this when checking for roles or multi tenancy. [More](#access) |
| `addresses` | `object` | Configuration for addresses collection and supported fields. [More](#addresses) |
| `carts` | `object` | Configuration for carts collection. [More](#carts) |
| `currencies` | `object` | Supported currencies by the store. [More](#currencies) |
| `customers` | `object` | Used to provide the customers slug. [More](#customers) |
| `inventory` | `boolean` `object` | Enable inventory tracking within Payload. Defaults to `true`. [More](#inventory) |
| `payments` | `object` | Configuring payments and supported payment methods. [More](#payments) |
| `products` | `object` | Configuration for products, variants collections and more. [More](#products) |
| `orders` | `object` | Configuration for orders collection. [More](#orders) |
| `transactions` | `boolean` `object` | Configuration for transactions collection. [More](#transactions) |
Note that the fields in overrides take a function that receives the default fields and returns an array of fields. This allows you to add fields to the collection.
```ts
ecommercePlugin({
access: {
adminOnly,
adminOnlyFieldAccess,
adminOrCustomerOwner,
adminOrPublishedStatus,
customerOnlyFieldAccess,
},
customers: {
slug: 'users',
},
payments: {
paymentMethods: [
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY!,
publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOKS_SIGNING_SECRET!,
}),
],
},
products: {
variants: {
variantsCollection: VariantsCollection,
},
productsCollection: ProductsCollection,
},
})
```
## Access
The plugin requires access control functions in order to restrict permissions to certain collections or fields. You can override these functions by providing your own in the `access` option.
| Option | Type | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------------- |
| `authenticatedOnly` | `Access` | Authenticated access only, provided by default. |
| `publicAccess` | `Access` | Public access, provided by default. |
| `adminOnly` | `Access` | Limited to only admin users. |
| `adminOnlyFieldAccess` | `FieldAccess` | Limited to only admin users, specifically for Field level access control. |
| `adminOrCustomerOwner` | `Access` | Is the owner of the document via the `customer` field or is an admin. |
| `adminOrPublishedStatus` | `Access` | The document is published or user is admin. |
| `customerOnlyFieldAccess` | `FieldAccess` | Limited to customers only, specifically for Field level access control. |
The default access control functions are:
```ts
access: {
authenticatedOnly: ({ req: { user } }) => Boolean(user),
publicAccess: () => true,
}
```
### authenticatedOnly
Access control to check if the user is authenticated. By default the following is provided:
```ts
authenticatedOnly: ({ req: { user } }) => Boolean(user)
```
### publicAccess
Access control to allow public access. By default the following is provided:
```ts
publicAccess: () => true
```
### adminOnly
Access control to check if the user has `admin` permissions.
Example:
```ts
adminOnly: ({ req: { user } }) => Boolean(user?.roles?.includes('admin'))
```
### adminOnlyFieldAccess
Field level access control to check if the user has `admin` permissions.
Example:
```ts
adminOnlyFieldAccess: ({ req: { user } }) =>
Boolean(user?.roles?.includes('admin'))
```
### adminOrCustomerOwner
Access control to check if the user has `admin` permissions or is the owner of the document via the `customer` field.
Example:
```ts
adminOrCustomerOwner: ({ req: { user } }) => {
if (user && Boolean(user?.roles?.includes('admin'))) {
return true
}
if (user?.id) {
return {
customer: {
equals: user.id,
},
}
}
return false
}
```
### adminOrPublishedStatus
Access control to check if the user has `admin` permissions or if the document is published.
Example:
```ts
adminOrPublishedStatus: ({ req: { user } }) => {
if (user && Boolean(user?.roles?.includes('admin'))) {
return true
}
return {
_status: {
equals: 'published',
},
}
}
```
### customerOnlyFieldAccess
Field level access control to check if the user has `customer` permissions.
Example:
```ts
customerOnlyFieldAccess: ({ req: { user } }) =>
Boolean(user?.roles?.includes('customer'))
```
## Addresses
The `addresses` option is used to configure the addresses collection and supported fields. Defaults to `true` which will create an `addresses` collection with default fields. It also takes an object:
| Option | Type | Description |
| ----------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `addressFields` | `FieldsOverride` | A function that is given the `defaultFields` as an argument and returns an array of fields. Use this to customise the supported fields for stored addresses. |
| `addressesCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `addresses` with a function where you can access the `defaultCollection` as an argument. |
| `supportedCountries` | `CountryType[]` | An array of supported countries in [ISO 3166-1 alpha-2 format](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). Defaults to all countries. |
You can add your own fields or modify the structure of the existing on in the collection. Example for overriding the default fields:
```ts
addresses: {
addressesCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'googleMapLocation',
label: 'Google Map Location',
type: 'text',
},
],
})
}
```
### supportedCountries
The `supportedCountries` option is an array of country codes in [ISO 3166-1 alpha-2 format](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). This is used to limit the countries that can be selected when creating or updating an address. If not provided, all countries will be supported. Currently used for storing addresses only.
You can import the default list of countries from the plugin:
```ts
import { defaultCountries } from '@payloadcms/plugin-ecommerce/client/react'
```
## Carts
The `carts` option is used to configure the carts collection. Defaults to `true` which will create a `carts` collection with default fields. It also takes an object:
| Option | Type | Description |
| ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `cartsCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `carts` with a function where you can access the `defaultCollection` as an argument. |
You can add your own fields or modify the structure of the existing on in the collection. Example for overriding the default fields:
```ts
carts: {
cartsCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'notes',
label: 'Notes',
type: 'textarea',
},
],
})
}
```
Carts are created when a customer adds their first item to the cart. The cart is then updated as they add or remove items. The cart is linked to a _Customer_ via the `customer` field. If the user is authenticated, this will be set to their user ID. If the user is not authenticated, this will be `null`.
If the user is not authenticated, the cart ID is stored in local storage and used to fetch the cart on subsequent requests. Access control by default works so that if the user is not authenticated then they can only access carts that have no customer linked to them.
## Customers
The `customers` option is required and is used to provide the customers collection slug. This collection is used to link orders, carts, and addresses to a customer.
| Option | Type | Description |
| ------ | -------- | ------------------------------------- |
| `slug` | `string` | The slug of the customers collection. |
While it's recommended to use just one collection for customers and your editors, you can use any collection you want for your customers. Just make sure that your access control is checking for the correct collections as well.
## Currencies
The `currencies` option is used to configure the supported currencies by the store. Defaults to `true` which will support `USD`. It also takes an object:
| Option | Type | Description |
| --------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `supportedCurrencies` | `Currency[]` | An array of supported currencies by the store. Defaults to `USD`. See [Currencies](#currencies-list) for available currencies. |
| `defaultCurrency` | `string` | The default currency code to use for the store. Defaults to the first currency. Must be one of the `supportedCurrencies` codes. |
The `Currency` type is as follows:
```ts
type Currency = {
code: string // The currency code in ISO 4217 format, e.g. 'USD'
decimals: number // The number of decimal places for the currency, e.g. 2 for USD
label: string // A human-readable label for the currency, e.g. 'US Dollar'
symbol: string // The currency symbol, e.g. '$'
}
```
For example, to support JYP in addition to USD:
```ts
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
import { USD } from '@payloadcms/plugin-ecommerce'
ecommercePlugin({
currencies: {
supportedCurrencies: [
USD,
{
code: 'JPY',
decimals: 0,
label: 'Japanese Yen',
symbol: '¥',
},
],
defaultCurrency: 'USD',
},
})
```
Note that adding a new currency could generate a new schema migration as it adds new prices fields in your products.
We currently support the following currencies out of the box:
- `USD`
- `EUR`
- `GBP`
You can import these from the plugin:
```ts
import { EUR } from '@payloadcms/plugin-ecommerce'
```
Note that adding new currencies here does not automatically enable them in
your payment gateway. Make sure to enable the currencies in your payment
gateway dashboard as well.
## Inventory
The `inventory` option is used to enable or disable inventory tracking within Payload. It defaults to `true`. It also takes an object:
| Option | Type | Description |
| ----------- | -------- | ------------------------------------------------------------------------- |
| `fieldName` | `string` | Override the field name used to track inventory. Defaults to `inventory`. |
For now it's quite rudimentary tracking with no integrations to 3rd party services. It will simply add an `inventory` field to the `variants` collection and decrement the inventory when an order is placed.
## Payments
The `payments` option is used to configure payments and supported payment methods.
| Option | Type | Description |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `paymentMethods` | `array` | An array of payment method adapters. Currently, only Stripe is supported. [More](#stripe-adapter) |
### Payment adapters
The plugin supports payment adapters to integrate with different payment gateways. Currently, only the [Stripe adapter](#stripe-adapter) is available. Adapters will provide a client side version as well with slightly different arguments.
Every adapter supports the following arguments in addition to their own:
| Argument | Type | Description |
| ---------------- | ---------------------------------- | ----------------------------------------------------------------------- |
| `label` | `string` | Human readabale label for this payment adapter. |
| `groupOverrides` | `GroupField` with `FieldsOverride` | Use this to override the available fields for the payment adapter type. |
Client side base arguments are the following:
| Argument | Type | Description |
| -------- | -------- | ----------------------------------------------- |
| `label` | `string` | Human readabale label for this payment adapter. |
See the [Stripe adapter](#stripe-adapter) for an example of client side arguments and the [React section](#react) for usage.
#### `groupOverrides`
The `groupOverrides` option allows you to customize the fields that are available for a specific payment adapter. It takes a `GroupField` object with a `fields` function that receives the default fields and returns an array of fields.
These fields are stored in transactions and can be used to collect additional information for the payment method. Stripe, for example, will track the `paymentIntentID`.
Example for overriding the default fields:
```ts
payments: {
paymentMethods: [
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY,
publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
webhookSecret: process.env.STRIPE_WEBHOOKS_SIGNING_SECRET,
groupOverrides: {
fields: ({ defaultFields }) => {
return [
...defaultFields,
{
name: 'customField',
label: 'Custom Field',
type: 'text',
},
]
}
}
}),
],
},
```
### Stripe Adapter
The Stripe adapter is used to integrate with the Stripe payment gateway. It requires a secret key, publishable key, and optionally webhook secret.
Note that Payload will not install the Stripe SDK package for you
automatically, so you will need to install it yourself:
```
pnpm add stripe
```
| Argument | Type | Description |
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `secretKey` | `string` | Required for communicating with the Stripe API in the backend. |
| `publishableKey` | `string` | Required for communicating with the Stripe API in the client side. |
| `webhookSecret` | `string` | The webhook secret used to verify incoming webhook requests from Stripe. |
| `webhooks` | `WebhookHandler[]` | An array of webhook handlers to register within Payload's REST API for Stripe to callback. |
| `apiVersion` | `string` | The Stripe API version to use. See [docs](https://stripe.com/docs/api/versioning). This will be deprecated soon by Stripe's SDK, configure the API version in your Stripe Dashboard. |
| `appInfo` | `object` | The application info to pass to Stripe. See [docs](https://stripe.com/docs/api/app_info). |
```ts
import { stripeAdapter } from '@payloadcms/plugin-ecommerce/payments/stripe'
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY!,
publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOKS_SIGNING_SECRET!,
})
```
#### Stripe `webhooks`
The `webhooks` option allows you to register custom webhook handlers for [Stripe events](https://docs.stripe.com/api/events). This is useful if you want to handle specific events that are not covered by the default handlers provided by the plugin.
```ts
stripeAdapter({
webhooks: {
'payment_intent.succeeded': ({ event, req }) => {
// Access to Payload's req object and event data
},
},
}),
```
#### Stripe client side
On the client side, you can use the `publishableKey` to initialize Stripe and handle payments. The client side version of the adapter only requires the `label` and `publishableKey` arguments. Never expose the `secretKey` or `webhookSecret` keys on the client side.
```ts
import { stripeAdapterClient } from '@payloadcms/plugin-ecommerce/payments/stripe'
{children}
```
## Products
The `products` option is used to configure the products and variants collections. Defaults to `true` which will create `products` and `variants` collections with default fields. It also takes an object:
| Option | Type | Description |
| ---------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productsCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `products` with a function where you can access the `defaultCollection` as an argument. |
| `variants` | `boolean` `object` | Configuration for the variants collection. Defaults to true. [More](#variants) |
| `validation` | `ProductsValidation` | Customise the validation used for checking products or variants before a transaction is created or a payment can be confirmed. [More](#products-validation) |
You can add your own fields or modify the structure of the existing on in the collections. Example for overriding the default fields:
```ts
products: {
productsCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'notes',
label: 'Notes',
type: 'textarea',
},
],
})
}
```
### Variants
The `variants` option is used to configure the variants collection. It takes an object:
| Option | Type | Description |
| ---------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `variantsCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `variants` with a function where you can access the `defaultCollection` as an argument. |
| `variantTypesCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `variantTypes` with a function where you can access the `defaultCollection` as an argument. |
| `variantOptionsCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `variantOptions` with a function where you can access the `defaultCollection` as an argument. |
You can add your own fields or modify the structure of the existing on in the collection. Example for overriding the default fields:
```ts
variants: {
variantsCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'customField',
label: 'Custom Field',
type: 'text',
},
],
})
}
```
The key differences between these collections:
- `variantTypes` are the types of variants that a product can have, e.g. Size, Color.
- `variantOptions` are the options for each variant type, e.g. Small, Medium, Large for Size.
- `variants` are the actual variants of a product, e.g. a T-Shirt in Size Small and Color Red.
### Products validation
We use an addition validation step when creating transactions or confirming payments to ensure that the products and variants being purchased are valid. This is to prevent issues such as purchasing a product that is out of stock or has been deleted.
You can customise this validation by providing your own validation function via the `validation` option which receives the following arguments:
| Option | Type | Description |
| ------------------ | ------------------ | -------------------------------------------------------------------------------------------------------- |
| `currenciesConfig` | `CurrenciesConfig` | The full currencies configuration provided in the plugin options. |
| `product` | `TypedCollection` | The product being purchased. |
| `variant` | `TypedCollection` | The variant being purchased, if a variant was selected for the product otherwise it will be `undefined`. |
| `quantity` | `number` | The quantity being purchased. |
| `currency` | `string` | The currency code being used for the purchase. |
The function should throw an error if the product or variant is not valid. If the function does not throw an error, the product or variant is considered valid.
The default validation function checks for the following:
- A currency is provided.
- The product or variant has a price in the selected currency.
- The product or variant has enough inventory for the requested quantity.
```ts
export const defaultProductsValidation: ProductsValidation = ({
currenciesConfig,
currency,
product,
quantity = 1,
variant,
}) => {
if (!currency) {
throw new Error('Currency must be provided for product validation.')
}
const priceField = `priceIn${currency.toUpperCase()}`
if (variant) {
if (!variant[priceField]) {
throw new Error(
`Variant with ID ${variant.id} does not have a price in ${currency}.`,
)
}
if (
variant.inventory === 0 ||
(variant.inventory && variant.inventory < quantity)
) {
throw new Error(
`Variant with ID ${variant.id} is out of stock or does not have enough inventory.`,
)
}
} else if (product) {
// Validate the product's details only if the variant is not provided as it can have its own inventory and price
if (!product[priceField]) {
throw new Error(`Product does not have a price in.`, {
cause: { code: MissingPrice, codes: [product.id, currency] },
})
}
if (
product.inventory === 0 ||
(product.inventory && product.inventory < quantity)
) {
throw new Error(
`Product is out of stock or does not have enough inventory.`,
{
cause: { code: OutOfStock, codes: [product.id] },
},
)
}
}
}
```
## Orders
The `orders` option is used to configure the orders collection. Defaults to `true` which will create an `orders` collection with default fields. It also takes an object:
| Option | Type | Description |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `ordersCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `orders` with a function where you can access the `defaultCollection` as an argument. |
You can add your own fields or modify the structure of the existing on in the collection. Example for overriding the default fields:
```ts
orders: {
ordersCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'notes',
label: 'Notes',
type: 'textarea',
},
],
})
}
```
## Transactions
The `transactions` option is used to configure the transactions collection. Defaults to `true` which will create a `transactions` collection with default fields. It also takes an object:
| Option | Type | Description |
| -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `transactionsCollectionOverride` | `CollectionOverride` | Allows you to override the collection for `transactions` with a function where you can access the `defaultCollection` as an argument. |
You can add your own fields or modify the structure of the existing on in the collection. Example for overriding the default fields:
```ts
transactions: {
transactionsCollectionOverride: ({ defaultCollection }) => ({
...defaultCollection,
fields: [
...defaultCollection.fields,
{
name: 'notes',
label: 'Notes',
type: 'textarea',
},
],
})
}
```
# Ecommerce Frontend
Source: https://payloadcms.com/docs/ecommerce/frontend
The package provides a set of React utilities to help you manage your ecommerce frontend. These include context providers, hooks, and components to handle carts, products, and payments.
The following hooks and components are available:
| Hook / Component | Description |
| ------------------- | ------------------------------------------------------------------------------ |
| `EcommerceProvider` | A context provider to wrap your application and provide the ecommerce context. |
| `useCart` | A hook to manage the cart state and actions. |
| `useAddresses` | A hook to fetch and manage products. |
| `usePayments` | A hook to manage the checkout process. |
| `useCurrency` | A hook to format prices based on the selected currency. |
| `useEcommerce` | A hook that encompasses all of the above in one. |
### EcommerceProvider
The `EcommerceProvider` component is used to wrap your application and provide the ecommerce context. It takes the following props:
| Prop | Type | Description |
| ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| `addressesSlug` | `string` | The slug of the addresses collection. Defaults to `addresses`. |
| `api` | `object` | API configuration for the internal fetches of the provider. [More](#api) |
| `cartsSlug` | `string` | The slug of the carts collection. Defaults to `carts`. |
| `children` | `ReactNode` | The child components that will have access to the ecommerce context. |
| `currenciesConfig` | `CurrenciesConfig` | Configuration for supported currencies. See [Currencies](./plugin#currencies). |
| `customersSlug` | `string` | The slug of the customers collection. Defaults to `users`. |
| `debug` | `boolean` | Enable or disable debug mode. This will send more information to the console. |
| `enableVariants` | `boolean` | Enable or disable product variants support. Defaults to `true`. |
| `paymentMethods` | `PaymentMethod[]` | An array of payment method adapters for the client side. See [Payment adapters](./plugin#payment-adapters). |
| `syncLocalStorage` | `boolean` `object` | Whether to sync the cart ID to local storage. Defaults to `true`. Takes an object for configuration |
Example usage:
```tsx
import { EcommerceProvider } from '@payloadcms/plugin-ecommerce/client/react'
// Import any payment adapters you want to use on the client side
import { stripeAdapterClient } from '@payloadcms/plugin-ecommerce/payments/stripe'
import { USD, EUR } from '@payloadcms/plugin-ecommerce'
export const Providers = () => (
{children}
)
```
#### api
The `api` prop is used to configure the API settings for the internal fetches of the provider. It takes an object with the following properties:
| Property | Type | Description |
| ----------------- | -------- | ----------------------------------------------------------------- |
| `apiRoute` | `string` | The base route for accessing the Payload API. Defaults to `/api`. |
| `serverURL` | `string` | The full URL of your Payload server. |
| `cartsFetchQuery` | `object` | Additional query parameters to include when fetching the cart. |
#### cartsFetchQuery
The `cartsFetchQuery` property allows you to specify additional query parameters to include when fetching the cart. This can be useful for including related data or customizing the response. This accepts:
| Property | Type | Description |
| ---------- | -------------- | --------------------------------------------------------------- |
| `depth` | `string` | Defaults to 0. [See Depth](../queries/depth) |
| `select` | `SelectType` | Select parameters. [See Select](../queries/select) |
| `populate` | `PopulateType` | Populate parameters. [See Populate](../queries/select#populate) |
Example usage:
```tsx
{children}
```
#### syncLocalStorage
The `syncLocalStorage` prop is used to enable or disable syncing the cart ID to local storage. This allows the cart to persist across page reloads and sessions. It defaults to `true`.
You can also provide an object with the following properties for more configuration:
| Property | Type | Description |
| -------- | -------- | ---------------------------------------------------------------------------- |
| `key` | `string` | The key to use for storing the cart ID in local storage. Defaults to `cart`. |
### useCart
The `useCart` hook is used to manage the cart state and actions. It provides methods to add, remove, and update items in the cart, as well as to fetch the current cart state. It has the following properties:
| Property | Type | Description |
| --------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `addItem` | `(item: CartItemInput, quantity?: number) => void` | Method to add an item to the cart, optionally accepts a quantity to add multiple at once. |
| `cart` | `Cart` `null` | The current cart state. Null or undefined if it doesn't exist. |
| `clearCart` | `() => void` | Method to clear the cart. |
| `decrementItem` | `(item: IDType) => void` | Method to decrement the quantity of an item. Will remove it entirely if it reaches 0. |
| `incrementItem` | `(item: IDType) => void` | Method to increment the quantity of an item. |
| `removeItem` | `(item: IDType) => void` | Method to remove an item from the cart. |
Example usage:
```tsx
import { useCart } from '@payloadcms/plugin-ecommerce/client/react'
const CartComponent = () => {
const { addItem, cart, clearCart, decrementItem, incrementItem, removeItem } =
useCart()
// Your component logic here
}
```
### useAddresses
The `useAddresses` hook is used to fetch and manage addresses. It provides methods to create, update, and delete addresses, as well as to fetch the list of addresses. It has the following properties:
| Property | Type | Description |
| --------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- |
| `addresses` | `Address[]` | The list of addresses, if any are available for the current user. |
| `createAddress` | `(data: Address) => Promise` | Method to create a new address. |
| `updateAddress` | `(addressID: IDType, data: Partial) => Promise` | Method to update an existing address by ID. |
Example usage:
```tsx
import { useAddresses } from '@payloadcms/plugin-ecommerce/client/react'
const AddressesComponent = () => {
const { addresses, createAddress, updateAddress } = useAddresses()
// Your component logic here
}
```
### usePayments
The `usePayments` hook is used to manage the checkout process. It provides methods to initiate payments, confirm orders, and handle payment status. It has the following properties:
| Property | Type | Description |
| ----------------------- | -------------------------- | ------------------------------------------------------------------- |
| `confirmOrder` | `(args) => Promise` | Method to confirm an order by ID. [More](#confirmOrder) |
| `initiatePayment` | `(args) => Promise` | Method to initiate a payment for an order. [More](#initiatePayment) |
| `paymentMethods` | `PaymentMethod[]` | The list of available payment methods. |
| `selectedPaymentMethod` | `PaymentMethod` | The currently selected payment method, if any. |
Example usage:
```tsx
import { usePayments } from '@payloadcms/plugin-ecommerce/client/react'
const CheckoutComponent = () => {
const {
confirmOrder,
initiatePayment,
paymentMethods,
selectedPaymentMethod,
} = usePayments()
// Your component logic here
}
```
#### confirmOrder
Use this method to confirm an order by its ID. It requires the payment method ID and will return the order ID.
```ts
try {
const data = await confirmOrder('stripe', {
additionalData: {
paymentIntentID: paymentIntent.id,
customerEmail,
},
})
// Return type will contain `orderID`
// use data to redirect to your order page
} catch (error) {
// handle error
}
```
If the payment gateway requires additional confirmations offsite then you will need another landing page to handle that. For example with Stripe you may need to use a callback URL, just make sure the relevant information is routed back.
This will mark the transaction as complete in the backend and create the order
for the user.
#### initiatePayment
Use this method to initiate a payment for an order. It requires the cart ID and the payment method ID. Depending on the payment method, additional data may be required. Depending on the payment method used you may need to provide additional data, for example with Stripe:
```ts
try {
const data = await initiatePayment('stripe', {
additionalData: {
customerEmail,
billingAddress,
shippingAddress,
},
})
} catch (error) {
// handle error
}
```
This function will hit the Payload API endpoint for `/stripe/initiate` and return the payment data required to complete the payment on the client side, which by default will include a `client_secret` to complete the payment with Stripe.js. The next step is to call the `confirmOrder` once payment is confirmed on the client side by Stripe.
At this step the cart is verified and a transaction is created in the backend
with the address details provided. No order is created yet until you call
`confirmOrder`, which should be done after payment is confirmed on the client
side or via webhooks if you opt for that approach instead.
### useCurrency
The `useCurrency` hook is used to format prices based on the selected currency. It provides methods to format prices and to get the current currency. It has the following properties:
| Property | Type | Description |
| ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `currenciesConfig` | `CurrenciesConfig` | The configuration for supported currencies. Directly matching the config provided to the Context Provider. [More](#ecommerceprovider) |
| `currency` | `Currency` | The currently selected currency. |
| `formatPrice` | `(amount: number) => string` | Method to format a price based on the selected currency. |
| `setCurrency` | `(currencyCode: string) => void` | Method to set the current currency by code. It will update all price formats when used in conjunction with the `formatPrice` utility. |
`formatPrice` in particular is very helpful as all prices are stored as integers to avoid any potential issues with decimal calculations, therefore on the frontend you can use this utility to format your price accounting for the currency and decimals. Example usage:
```tsx
import { useCurrency } from '@payloadcms/plugin-ecommerce/client/react'
const PriceComponent = ({ amount }) => {
const { currenciesConfig, currency, setCurrency } = useCurrency()
return
{formatPrice(amount)}
}
```
### useEcommerce
The `useEcommerce` hook encompasses all of the above hooks in one. It provides access to the cart, addresses, and payments hooks.
Example usage:
```tsx
import { useEcommerce } from '@payloadcms/plugin-ecommerce/client/react'
const EcommerceComponent = () => {
const { cart, addresses, selectedPaymentMethod } = useEcommerce()
// Your component logic here
}
```
# Payment Adapters
Source: https://payloadcms.com/docs/ecommerce/payments
A deeper look into the payment adapter pattern used by the Ecommerce Plugin, and how to create your own.
The current list of supported payment adapters are:
- [Stripe](#stripe)
## REST API
The plugin will create REST API endpoints for each payment adapter you add to your configuration. The endpoints will be available at `/api/payments/{provider_name}/{action}` where `provider_name` is the name of the payment adapter and `action` is one of the following:
| Action | Method | Description |
| --------------- | ------ | --------------------------------------------------------------------------------------------------- |
| `initiate` | POST | Initiate a payment for an order. See [initiatePayment](#initiatePayment) for more details. |
| `confirm-order` | POST | Confirm an order after a payment has been made. See [confirmOrder](#confirmOrder) for more details. |
## Stripe
Out of the box we integrate with Stripe to handle one-off purchases. To use Stripe, you will need to install the Stripe package:
```bash
pnpm add stripe
```
We recommend at least `18.5.0` to ensure compatibility with the plugin.
Then, in your `plugins` array of your [Payload Config](../configuration/overview), call the plugin with:
```ts
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
import { stripeAdapter } from '@payloadcms/plugin-ecommerce/payments/stripe'
import { buildConfig } from 'payload'
export default buildConfig({
// Payload config...
plugins: [
ecommercePlugin({
// rest of config...
payments: {
paymentMethods: [
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY,
publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
// Optional - only required if you want to use webhooks
webhookSecret: process.env.STRIPE_WEBHOOKS_SIGNING_SECRET,
}),
],
},
}),
],
})
```
### Configuration
The Stripe payment adapter takes the following configuration options:
| Option | Type | Description |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| secretKey | `string` | Your Stripe Secret Key, found in the [Stripe Dashboard](https://dashboard.stripe.com/apikeys). |
| publishableKey | `string` | Your Stripe Publishable Key, found in the [Stripe Dashboard](https://dashboard.stripe.com/apikeys). |
| webhookSecret | `string` | (Optional) Your Stripe Webhooks Signing Secret, found in the [Stripe Dashboard](https://dashboard.stripe.com/webhooks). Required if you want to use webhooks. |
| appInfo | `object` | (Optional) An object containing `name` and `version` properties to identify your application to Stripe. |
| webhooks | `object` | (Optional) An object where the keys are Stripe event types and the values are functions that will be called when that event is received. See [Webhooks](#stripe-webhooks) for more details. |
| groupOverrides | `object` | (Optional) An object to override the default fields of the payment group. See [Payment Fields](./advanced#payment-fields) for more details. |
### Stripe Webhooks
You can also add your own webhooks to handle [events from Stripe](https://docs.stripe.com/api/events). This is optional and the plugin internally does not use webhooks for any core functionality. It receives the following arguments:
| Argument | Type | Description |
| -------- | ---------------- | ------------------------------- |
| event | `Stripe.Event` | The Stripe event object |
| req | `PayloadRequest` | The Payload request object |
| stripe | `Stripe` | The initialized Stripe instance |
You can add a webhook like so:
```ts
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
import { stripeAdapter } from '@payloadcms/plugin-ecommerce/payments/stripe'
import { buildConfig } from 'payload'
export default buildConfig({
// Payload config...
plugins: [
ecommercePlugin({
// rest of config...
payments: {
paymentMethods: [
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY,
publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
// Required
webhookSecret: process.env.STRIPE_WEBHOOKS_SIGNING_SECRET,
webhooks: {
'payment_intent.succeeded': ({ event, req }) => {
console.log({ event, data: event.data.object })
req.payload.logger.info('Payment succeeded')
},
},
}),
],
},
}),
],
})
```
To use webhooks you also need to have them configured in your Stripe Dashboard.
You can use the [Stripe CLI](https://stripe.com/docs/stripe-cli) to forward webhooks to your local development environment.
### Frontend usage
The most straightforward way to use Stripe on the frontend is with the `EcommerceProvider` component and the `stripeAdapterClient` function. Wrap your application in the provider and pass in the Stripe adapter with your publishable key:
```ts
import { EcommerceProvider } from '@payloadcms/plugin-ecommerce/client/react'
import { stripeAdapterClient } from '@payloadcms/plugin-ecommerce/payments/stripe'
{children}
```
Then you can use the `usePayments` hook to access the `initiatePayment` and `confirmOrder` functions, see the [Frontend docs](./frontend#usePayments) for more details.
## Making your own Payment Adapter
You can make your own payment adapter by implementing the `PaymentAdapter` interface. This interface requires you to implement the following methods:
| Property | Type | Description |
| ----------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | The name of the payment method. This will be used to identify the payment method in the API and on the frontend. |
| `label` | `string` | (Optional) A human-readable label for the payment method. This will be used in the admin panel and on the frontend. |
| `initiatePayment` | `(args: InitiatePaymentArgs) => Promise` | The function that is called via the `/api/payments/{provider_name}/initiate` endpoint to initiate a payment for an order. [More](#initiatePayment) |
| `confirmOrder` | `(args: ConfirmOrderArgs) => Promise` | The function that is called via the `/api/payments/{provider_name}/confirm-order` endpoint to confirm an order after a payment has been made. [More](#confirmOrder) |
| `endpoints` | `Endpoint[]` | (Optional) An array of endpoints to be bootstrapped to Payload's API in order to support the payment method. All API paths are relative to `/api/payments/{provider_name}` |
| `group` | `GroupField` | A group field config to be used in transactions to track the necessary data for the payment processor, eg. PaymentIntentID for Stripe. See [Payment Fields](#payment-fields) for more details. |
The arguments can be extended but should always include the `PaymentAdapterArgs` type which has the following types:
| Property | Type | Description |
| ---------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `label` | `string` | (Optional) Allow overriding the default UI label for this adaper. |
| `groupOverrides` | `FieldsOverride` | (Optional) Allow overriding the default fields of the payment group. See [Payment Fields](#payment-fields) for more details. |
#### initiatePayment
The `initiatePayment` function is called when a payment is initiated. At this step the transaction is created with a status "Processing", an abandoned purchaase will leave this transaction in this state. It receives an object with the following properties:
| Property | Type | Description |
| ------------------ | ---------------- | --------------------------------------------- |
| `transactionsSlug` | `Transaction` | The transaction being processed. |
| `data` | `object` | The cart associated with the transaction. |
| `customersSlug` | `string` | The customer associated with the transaction. |
| `req` | `PayloadRequest` | The Payload request object. |
The data object will contain the following properties:
| Property | Type | Description |
| ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
| `billingAddress` | `Address` | The billing address associated with the transaction. |
| `shippingAddress` | `Address` | (Optional) The shipping address associated with the transaction. If this is missing then use the billing address. |
| `cart` | `Cart` | The cart collection item. |
| `customerEmail` | `string` | In the case that `req.user` is missing, `customerEmail` should be required in order to process guest checkouts. |
| `currency` | `string` | The currency for the cart associated with the transaction. |
The return type then only needs to contain the following properties though the type supports any additional data returned as needed for the frontend:
| Property | Type | Description |
| --------- | -------- | ----------------------------------------------- |
| `message` | `string` | A success message to be returned to the client. |
At any point in the function you can throw an error to return a 4xx or 5xx response to the client.
A heavily simplified example of implementing `initiatePayment` could look like:
```ts
import {
PaymentAdapter,
PaymentAdapterArgs,
} from '@payloadcms/plugin-ecommerce'
import Stripe from 'stripe'
export const initiatePayment: NonNullable['initiatePayment'] =
async ({ data, req, transactionsSlug }) => {
const payload = req.payload
// Check for any required data
const currency = data.currency
const cart = data.cart
if (!currency) {
throw new Error('Currency is required.')
}
const stripe = new Stripe(secretKey)
try {
let customer = (
await stripe.customers.list({
email: customerEmail,
})
).data[0]
// Ensure stripe has a customer for this email
if (!customer?.id) {
customer = await stripe.customers.create({
email: customerEmail,
})
}
const shippingAddressAsString = JSON.stringify(shippingAddressFromData)
const paymentIntent = await stripe.paymentIntents.create()
// Create a transaction for the payment intent in the database
const transaction = await payload.create({
collection: transactionsSlug,
data: {},
})
// Return the client_secret so that the client can complete the payment
const returnData: InitiatePaymentReturnType = {
clientSecret: paymentIntent.client_secret || '',
message: 'Payment initiated successfully',
paymentIntentID: paymentIntent.id,
}
return returnData
} catch (error) {
payload.logger.error(error, 'Error initiating payment with Stripe')
throw new Error(
error instanceof Error
? error.message
: 'Unknown error initiating payment',
)
}
}
```
#### confirmOrder
The `confirmOrder` function is called after a payment is completed on the frontend and at this step the order is created in Payload. It receives the following properties:
| Property | Type | Description |
| ------------------ | ---------------- | ----------------------------------------- |
| `ordersSlug` | `string` | The orders collection slug. |
| `transactionsSlug` | `string` | The transactions collection slug. |
| `cartsSlug` | `string` | The carts collection slug. |
| `customersSlug` | `string` | The customers collection slug. |
| `data` | `object` | The cart associated with the transaction. |
| `req` | `PayloadRequest` | The Payload request object. |
The data object will contain any data the frontend chooses to send through and at a minimum the following:
| Property | Type | Description |
| --------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `customerEmail` | `string` | In the case that `req.user` is missing, `customerEmail` should be required in order to process guest checkouts. |
The return type can also contain any additional data with a minimum of the following:
| Property | Type | Description |
| --------------- | -------- | ----------------------------------------------- |
| `message` | `string` | A success message to be returned to the client. |
| `orderID` | `string` | The ID of the created order. |
| `transactionID` | `string` | The ID of the associated transaction. |
A heavily simplified example of implementing `confirmOrder` could look like:
```ts
import {
PaymentAdapter,
PaymentAdapterArgs,
} from '@payloadcms/plugin-ecommerce'
import Stripe from 'stripe'
export const confirmOrder: NonNullable['confirmOrder'] =
async ({
data,
ordersSlug = 'orders',
req,
transactionsSlug = 'transactions',
}) => {
const payload = req.payload
const customerEmail = data.customerEmail
const paymentIntentID = data.paymentIntentID as string
const stripe = new Stripe(secretKey)
try {
// Find our existing transaction by the payment intent ID
const transactionsResults = await payload.find({
collection: transactionsSlug,
where: {
'stripe.paymentIntentID': {
equals: paymentIntentID,
},
},
})
const transaction = transactionsResults.docs[0]
// Verify the payment intent exists and retrieve it
const paymentIntent =
await stripe.paymentIntents.retrieve(paymentIntentID)
// Create the order in the database
const order = await payload.create({
collection: ordersSlug,
data: {},
})
const timestamp = new Date().toISOString()
// Update the cart to mark it as purchased, this will prevent further updates to the cart
await payload.update({
id: cartID,
collection: 'carts',
data: {
purchasedAt: timestamp,
},
})
// Update the transaction with the order ID and mark as succeeded
await payload.update({
id: transaction.id,
collection: transactionsSlug,
data: {
order: order.id,
status: 'succeeded',
},
})
return {
message: 'Payment initiated successfully',
orderID: order.id,
transactionID: transaction.id,
}
} catch (error) {
payload.logger.error(error, 'Error initiating payment with Stripe')
}
}
```
#### Payment Fields
Payment fields are used primarily on the transactions collection to store information about the payment method used. Each payment adapter must provide a `group` field which will be used to store this information.
For example, the Stripe adapter provides the following group field:
```ts
const groupField: GroupField = {
name: 'stripe',
type: 'group',
admin: {
condition: (data) => {
const path = 'paymentMethod'
return data?.[path] === 'stripe'
},
},
fields: [
{
name: 'customerID',
type: 'text',
label: 'Stripe Customer ID',
},
{
name: 'paymentIntentID',
type: 'text',
label: 'Stripe PaymentIntent ID',
},
],
}
```
### Client side Payment Adapter
The client side adapter should implement the `PaymentAdapterClient` interface:
| Property | Type | Description |
| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | The name of the payment method. This will be used to identify the payment method in the API and on the frontend. |
| `label` | `string` | (Optional) A human-readable label for the payment method. This can be used as a human readable format. |
| `initiatePayment` | `boolean` | Flag to toggle on the EcommerceProvider's ability to call the `/api/payments/{provider_name}/initiate` endpoint. If your payment method does not require this step, set this to `false`. |
| `confirmOrder` | `boolean` | Flag to toggle on the EcommerceProvider's ability to call the `/api/payments/{provider_name}/confirm-order` endpoint. If your payment method does not require this step, set this to `false`. |
And for the args use the `PaymentAdapterClientArgs` type:
| Property | Type | Description |
| -------- | -------- | ----------------------------------------------------------------- |
| `label` | `string` | (Optional) Allow overriding the default UI label for this adaper. |
## Best Practices
Always handle sensitive operations like creating payment intents and confirming payments on the server side. Use webhooks to listen for events from Stripe and update your orders accordingly. Never expose your secret key on the frontend. By default Nextjs will only expose environment variables prefixed with `NEXT_PUBLIC_` to the client.
While we validate the products and prices on the server side when creating a payment intent, you should override the validation function to add any additional checks you may need for your specific use case.
You are safe to pass the ID of a transaction to the frontend however you shouldn't pass any sensitive information or the transaction object itself.
When passing price information to your payment provider it should always come from the server and it should be verified against the products in your database. Never trust price information coming from the client.
When using webhooks, ensure that you verify the webhook signatures to confirm that the requests are genuinely from Stripe. This helps prevent unauthorized access and potential security vulnerabilities.
# Advanced uses and examples
Source: https://payloadcms.com/docs/ecommerce/advanced
The plugin also exposes its internal utilities so that you can use only the parts that you need without using the entire plugin. This is useful if you want to build your own ecommerce solution on top of Payload.
## Using only the collections
You can import the collections directly from the plugin and add them to your Payload configuration. This way, you can use the collections without using the entire plugin:
| Name | Collection | Description |
| -------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `createAddressesCollection` | `addresses` | Used for customer addresses (like shipping and billing). [More](#createAddressesCollection) |
| `createCartsCollection` | `carts` | Carts can be used by customers, guests and once purchased are kept for records and analytics. [More](#createCartsCollection) |
| `createOrdersCollection` | `orders` | Orders are used to store customer-side information and are related to at least one transaction. [More](#createOrdersCollection) |
| `createTransactionsCollection` | `transactions` | Handles payment information accessable by admins only, related to Orders. [More](#createTransactionsCollection) |
| `createProductsCollection` | `products` | All the product information lives here, contains prices, relations to Variant Types and joins to Variants. [More](#createProductsCollection) |
| `createVariantsCollection` | `variants` | Product variants, unique purchasable items that are linked to a product and Variant Options. [More](#createVariantsCollection) |
| `createVariantTypesCollection` | `variantTypes` | A taxonomy used by Products to relate Variant Options together. An example of a Variant Type is "size". [More](#createVariantTypesCollection) |
| `createVariantOptionsCollection` | `variantOptions` | Related to a Variant Type to handle a unique property of it. An example of a Variant Option is "small". [More](#createVariantOptionsCollection) |
### createAddressesCollection
Use this to create the `addresses` collection. This collection is used to store customer addresses. It takes the following properties:
| Property | Type | Description |
| -------------------- | --------------- | --------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `addressFields` | `Field[]` | Custom fields to add to the address. |
| `customersSlug` | `string` | (Optional) Slug of the customers collection. Defaults to `customers`. |
| `supportedCountries` | `CountryType[]` | (Optional) List of supported countries. Defaults to all countries. |
The access object can contain the following properties:
| Property | Type | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adminOrCustomerOwner` | `Access` | Access control to check if the user has `admin` permissions or is the owner of the document via the `customer` field. Used to limit read, update or delete to only the customers that own this address. |
| `authenticatedOnly` | `Access` | Access control to check if the user is authenticated. Use on the `create` access to allow any customer to create a new address. |
| `customerOnlyFieldAccess` | `FieldAccess` | Field level access control to check if the user has `customer` permissions. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createAddressesCollection } from 'payload-plugin-ecommerce'
const Addresses = createAddressesCollection({
access: {
adminOrCustomerOwner,
authenticatedOnly,
customerOnlyFieldAccess,
},
addressFields: [
{
name: 'company',
type: 'text',
label: 'Company',
},
],
})
```
### createCartsCollection
Use this to create the `carts` collection to store customer carts. It takes the following properties:
| Property | Type | Description |
| ------------------ | ------------------ | ----------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `customersSlug` | `string` | (Optional) Slug of the customers collection. Defaults to `customers`. |
| `productsSlug` | `string` | (Optional) Slug of the products collection. Defaults to `products`. |
| `variantsSlug` | `string` | (Optional) Slug of the variants collection. Defaults to `variants`. |
| `enableVariants` | `boolean` | (Optional) Whether to enable variants in the cart. Defaults to `true`. |
| `currenciesConfig` | `CurrenciesConfig` | (Optional) Currencies configuration to enable a subtotal to be tracked. |
The access object can contain the following properties:
| Property | Type | Description |
| ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adminOrCustomerOwner` | `Access` | Access control to check if the user has `admin` permissions or is the owner of the document via the `customer` field. Used to limit read, update or delete to only the customers that own this cart. |
| `publicAccess` | `Access` | Allow anyone to create a new cart, useful for guests. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createCartsCollection } from 'payload-plugin-ecommerce'
const Carts = createCartsCollection({
access: {
adminOrCustomerOwner,
publicAccess,
},
enableVariants: true,
currenciesConfig: {
defaultCurrency: 'usd',
currencies: [
{
code: 'usd',
symbol: '$',
},
{
code: 'eur',
symbol: '€',
},
],
},
})
```
### createOrdersCollection
Use this to create the `orders` collection to store customer orders. It takes the following properties:
| Property | Type | Description |
| ------------------ | ------------------ | --------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `customersSlug` | `string` | (Optional) Slug of the customers collection. Defaults to `customers`. |
| `transactionsSlug` | `string` | (Optional) Slug of the transactions collection. Defaults to `transactions`. |
| `productsSlug` | `string` | (Optional) Slug of the products collection. Defaults to `products`. |
| `variantsSlug` | `string` | (Optional) Slug of the variants collection. Defaults to `variants`. |
| `enableVariants` | `boolean` | (Optional) Whether to enable variants in the order. Defaults to `true`. |
| `currenciesConfig` | `CurrenciesConfig` | (Optional) Currencies configuration to enable the amount to be tracked. |
| `addressFields` | `Field[]` | (Optional) The fields to be used for the shipping address. |
The access object can contain the following properties:
| Property | Type | Description |
| ---------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adminOrCustomerOwner` | `Access` | Access control to check if the user has `admin` permissions or is the owner of the document via the `customer` field. Used to limit read to only the customers that own this order. |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit create, update and delete access to only admins. |
| `adminOnlyFieldAccess` | `FieldAccess` | Field level access control to check if the user has `admin` permissions. Limits the transaction ID field to admins only. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createOrdersCollection } from 'payload-plugin-ecommerce'
const Orders = createOrdersCollection({
access: {
adminOrCustomerOwner,
adminOnly,
adminOnlyFieldAccess,
},
enableVariants: true,
currenciesConfig: {
defaultCurrency: 'usd',
currencies: [
{
code: 'usd',
symbol: '$',
},
{
code: 'eur',
symbol: '€',
},
],
},
addressFields: [
{
name: 'deliveryInstructions',
type: 'text',
label: 'Delivery Instructions',
},
],
})
```
### createTransactionsCollection
Use this to create the `transactions` collection to store payment transactions. It takes the following properties:
| Property | Type | Description |
| ------------------ | ------------------ | ----------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `customersSlug` | `string` | (Optional) Slug of the customers collection. Defaults to `customers`. |
| `cartsSlug` | `string` | (Optional) Slug of the carts collection. Defaults to `carts`. |
| `ordersSlug` | `string` | (Optional) Slug of the orders collection. Defaults to `orders`. |
| `productsSlug` | `string` | (Optional) Slug of the products collection. Defaults to `products`. |
| `variantsSlug` | `string` | (Optional) Slug of the variants collection. Defaults to `variants`. |
| `enableVariants` | `boolean` | (Optional) Whether to enable variants in the transaction. Defaults to `true`. |
| `currenciesConfig` | `CurrenciesConfig` | (Optional) Currencies configuration to enable the amount to be tracked. |
| `addressFields` | `Field[]` | (Optional) The fields to be used for the billing address. |
| `paymentMethods` | `PaymentAdapter[]` | (Optional) The payment methods to be used for the transaction. |
The access object can contain the following properties:
| Property | Type | Description |
| ----------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit all access to only admins. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createTransactionsCollection } from 'payload-plugin-ecommerce'
const Transactions = createTransactionsCollection({
access: {
adminOnly,
},
enableVariants: true,
currenciesConfig: {
defaultCurrency: 'usd',
currencies: [
{
code: 'usd',
symbol: '$',
},
{
code: 'eur',
symbol: '€',
},
],
},
addressFields: [
{
name: 'billingInstructions',
type: 'text',
label: 'Billing Instructions',
},
],
paymentMethods: [
// Add your payment adapters here
],
})
```
### createProductsCollection
Use this to create the `products` collection to store products. It takes the following properties:
| Property | Type | Description |
| ------------------ | --------------------------- | -------------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `variantsSlug` | `string` | (Optional) Slug of the variants collection. Defaults to `variants`. |
| `variantTypesSlug` | `string` | (Optional) Slug of the variant types collection. Defaults to `variantTypes`. |
| `enableVariants` | `boolean` | (Optional) Whether to enable variants on products. Defaults to `true`. |
| `currenciesConfig` | `CurrenciesConfig` | (Optional) Currencies configuration to enable price fields. |
| `inventory` | `boolean` `InventoryConfig` | (Optional) Inventory configuration to enable stock tracking. Defaults to `true`. |
The access object can contain the following properties:
| Property | Type | Description |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit create, update or delete to only admins. |
| `adminOrPublishedStatus` | `Access` | Access control to check if the user has `admin` permissions or if the product has a `published` status. Used to limit read access to published products for non-admins. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createProductsCollection } from 'payload-plugin-ecommerce'
const Products = createProductsCollection({
access: {
adminOnly,
adminOrPublishedStatus,
},
enableVariants: true,
currenciesConfig: {
defaultCurrency: 'usd',
currencies: [
{
code: 'usd',
symbol: '$',
},
{
code: 'eur',
symbol: '€',
},
],
},
inventory: {
enabled: true,
trackByVariant: true,
lowStockThreshold: 5,
},
})
```
### createVariantsCollection
Use this to create the `variants` collection to store product variants. It takes the following properties:
| Property | Type | Description |
| -------------------- | --------------------------- | -------------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `productsSlug` | `string` | (Optional) Slug of the products collection. Defaults to `products`. |
| `variantOptionsSlug` | `string` | (Optional) Slug of the variant options collection. Defaults to `variantOptions`. |
| `currenciesConfig` | `CurrenciesConfig` | (Optional) Currencies configuration to enable price fields. |
| `inventory` | `boolean` `InventoryConfig` | (Optional) Inventory configuration to enable stock tracking. Defaults to `true`. |
The access object can contain the following properties:
| Property | Type | Description |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit all access to only admins. |
| `adminOrPublishedStatus` | `Access` | Access control to check if the user has `admin` permissions or if the related product has a `published` status. Used to limit read access to variants of published products for non-admins. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createVariantsCollection } from 'payload-plugin-ecommerce'
const Variants = createVariantsCollection({
access: {
adminOnly,
adminOrPublishedStatus,
},
currenciesConfig: {
defaultCurrency: 'usd',
currencies: [
{
code: 'usd',
symbol: '$',
},
{
code: 'eur',
symbol: '€',
},
],
},
inventory: {
enabled: true,
lowStockThreshold: 5,
},
})
```
### createVariantTypesCollection
Use this to create the `variantTypes` collection to store variant types. It takes the following properties:
| Property | Type | Description |
| -------------------- | -------- | -------------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `variantOptionsSlug` | `string` | (Optional) Slug of the variant options collection. Defaults to `variantOptions`. |
The access object can contain the following properties:
| Property | Type | Description |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit all access to only admins. |
| `publicAccess` | `Access` | Allow anyone to read variant types. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createVariantTypesCollection } from 'payload-plugin-ecommerce'
const VariantTypes = createVariantTypesCollection({
access: {
adminOnly,
publicAccess,
},
})
```
### createVariantOptionsCollection
Use this to create the `variantOptions` collection to store variant options. It takes the following properties:
| Property | Type | Description |
| ------------------ | -------- | ---------------------------------------------------------------------------- |
| `access` | `object` | Access control for the collection. |
| `variantTypesSlug` | `string` | (Optional) Slug of the variant types collection. Defaults to `variantTypes`. |
The access object can contain the following properties:
| Property | Type | Description |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `adminOnly` | `Access` | Access control to check if the user has `admin` permissions. Used to limit all access to only admins. |
| `publicAccess` | `Access` | Allow anyone to read variant options. |
See the [access control section](./plugin#access) for more details on each of these functions.
Example usage:
```ts
import { createVariantOptionsCollection } from 'payload-plugin-ecommerce'
const VariantOptions = createVariantOptionsCollection({
access: {
adminOnly,
publicAccess,
},
})
```
## Typescript
There are several common types that you'll come across when working with this package. These are export from the package as well and are used across individual utilities as well.
### CurrenciesConfig
Defines the supported currencies in Payload and the frontend. It has the following properties:
| Property | Type | Description |
| ----------------- | ---------------- | --------------------------------------------------------------------------------- |
| `defaultCurrency` | `string` | The default currency code. Must match one of the codes in the `currencies` array. |
| `currencies` | `CurrencyType[]` | An array of supported currencies. Each currency must have a unique code. |
### Currency
Defines a currency to be used in the application. It has the following properties:
| Property | Type | Description |
| ---------- | -------- | ------------------------------------------------ |
| `code` | `string` | The ISO 4217 currency code. Example `'usd'`. |
| `symbol` | `string` | The symbol of the currency. Example `'$'` |
| `label` | `string` | The name of the currency. Example `'USD'` |
| `decimals` | `number` | The number of decimal places to use. Example `2` |
The decimals is very important to provide as we store all prices as integers to avoid floating point issues. For example, if you're using USD, you would store a price of $10.00 as `1000` (10 \* 10^2), so when formatting the price for display we need to know how many decimal places the currency supports.
### CountryType
Used to define a country in address fields and supported countries lists. It has the following properties:
| Property | Type | Description |
| -------- | -------- | ---------------------------- |
| `value` | `string` | The ISO 3166-1 alpha-2 code. |
| `label` | `string` | The name of the country. |
### InventoryConfig
It's used to customise the inventory tracking settings on products and variants. It has the following properties:
| Property | Type | Description |
| ----------- | -------- | ------------------------------------------------------------------------------------ |
| `fieldName` | `string` | (Optional) The name of the field to use for tracking stock. Defaults to `inventory`. |
# Examples
Source: https://payloadcms.com/docs/examples/overview
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 precisely what is going on.
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
- [Custom Components](https://github.com/payloadcms/payload/tree/main/examples/custom-components)
- [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)
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
- [Tailwind / Shadcn-ui](https://github.com/payloadcms/payload/tree/main/examples/tailwind-shadcn-ui)
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
If you'd like to run the examples, you can use `create-payload-app` to create a project from one:
```sh
npx create-payload-app --example example_name
```
We are adding new examples every day, so if your particular use case is not demonstrated in any existing 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.
# Vercel Content Link
Source: https://payloadcms.com/docs/integrations/vercel-content-link
[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.

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.
## 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
```
## Enabling Content Source Maps
Now in your Next.js app, you need to add the `encodeSourceMaps` query parameter to your API requests. This will tell Payload to include the Content Source Maps in the API response.
**Note:** For performance reasons, this should only be done when in draft mode
or on preview deployments.
#### REST API
If you're using the REST API, include the `?encodeSourceMaps=true` search parameter.
```ts
if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
const res = await fetch(
`${process.env.NEXT_PUBLIC_PAYLOAD_CMS_URL}/api/pages?encodeSourceMaps=true&where[slug][equals]=${slug}`,
)
}
```
#### Local API
If you're using the Local API, include the `encodeSourceMaps` via the `context` property.
```ts
if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
const res = await payload.find({
collection: 'pages',
where: {
slug: {
equals: slug,
},
},
context: {
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.

## Troubleshooting
### Date Fields
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 and array fields
All `blocks` and `array` fields by definition do not have plain text strings to encode. For this reason, they are automatically given an additional `_encodedSourceMap` property, which you can use to enable Content Link on entire _sections_ of your site.
You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
```ts
{_encodedSourceMap}
{children}
```
# Building without a DB connection
Source: https://payloadcms.com/docs/production/building-without-a-db-connection
One of the most common problems when building a site for production, especially with Docker - is the DB connection requirement.
The important note is that Payload by itself does not have this requirement, But [Next.js' SSG ](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation) does if any of your route segments have SSG enabled (which is default, unless you opted out or used a [Dynamic API](https://nextjs.org/docs/app/deep-dive/caching#dynamic-apis)) and use the Payload Local API.
Solutions:
## Using the experimental-build-mode Next.js build flag
You can run Next.js build using the `pnpm next build --experimental-build-mode compile` command to only compile the code without static generation, which does not require a DB connection. In that case, your pages will be rendered dynamically, but after that, you can still generate static pages using the `pnpm next build --experimental-build-mode generate` command when you have a DB connection.
When running `pnpm next build --experimental-build-mode compile`, environment variables prefixed with `NEXT_PUBLIC` will not be inlined and will be `undefined` on the client. To make these variables available, either run `pnpm next build --experimental-build-mode generate` if a DB connection is available, or use `pnpm next build --experimental-build-mode generate-env` if you do not have a DB connection.
[Next.js documentation](https://nextjs.org/docs/pages/api-reference/cli/next#next-build-options)
## Opting-out of SSG
You can opt out of SSG by adding this all the route segment files:
```ts
export const dynamic = 'force-dynamic'
```
**Note that it will disable static optimization and your site will be slower**.
More on [Next.js documentation](https://nextjs.org/docs/app/deep-dive/caching#opting-out-2)
# Production Deployment
Source: https://payloadcms.com/docs/production/deployment
So you've developed a Payload app, it's fully tested, and running great
locally. Now it's time to launch. **Awesome! Great work!** Now, what's next?
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 database](#database)
1. [Permanent File Storage](#file-storage)
1. [Docker](#docker)
Payload can be deployed _anywhere that Next.js can run_ - including Vercel, Netlify, SST, DigitalOcean, AWS, and more. Because it's open source, you can self-host it.
But it's important to remember that most Payload projects will also need a database, file storage, an email provider, and a CDN. Make sure you have all of the requirements that your project needs, no matter what deployment platform you choose.
## Basics
Payload runs fully in Next.js, so the [Next.js build process](https://nextjs.org/docs/app/building-your-application/deploying) is used for building Payload. If you've used `create-payload-app` to create your project, executing the `build`
npm script will build Payload for production.
## Security
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.
### 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.
**By default, all Access Control functions require that a user is successfully logged in to Payload to create, read, update, or delete data.**
But, if you allow public user registration, for example, you will want to make sure that your access control functions are more strict - permitting **only appropriate users** to perform appropriate actions.
### Running in Production
Depending on where you deploy Payload, you may need to provide a start script to your deployment platform in order to start up Payload in production mode.
Note that this is different than running `next dev`. Generally, Next.js apps come configured with a `start` script which runs `next start`.
### Secure Cookie Settings
You should be using an SSL certificate for production Payload instances, which means you
can [enable secure cookies](../authentication/overview) 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, GraphQL query complexity limits, max `depth` settings, and
more. [Click here to learn more](../production/preventing-abuse).
## Database
Payload can be used with any Postgres database or MongoDB-compatible database including AWS DocumentDB or Azure Cosmos DB. Make sure your production environment has access to the database that Payload uses.
Out of the box, Payload templates pass the `process.env.DATABASE_URI` environment variable to its database adapters, so make sure you've got that environment variable (and all others that you use) assigned in your deployment platform.
### DocumentDB
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 [indexSortableFields](../configuration/overview) option.
## File storage
If you are using Payload to [manage file uploads](../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
**Warning:**
If you rely on Payload's **Upload** 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.
### Using cloud storage providers
If you don't use Payload's `upload` functionality, you can completely disregard this section.
But, if you do, and you still want to use an ephemeral filesystem provider, you can use one of Payload's official cloud storage plugins or write your own to save the files your users upload to a more permanent storage solution like Amazon S3 or DigitalOcean Spaces.
Payload provides a list of official cloud storage adapters for you to use:
- [Azure Blob Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-azure)
- [Google Cloud Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-gcs)
- [AWS S3](https://github.com/payloadcms/payload/tree/main/packages/storage-s3)
- [Uploadthing](https://github.com/payloadcms/payload/tree/main/packages/storage-uploadthing)
- [Vercel Blob Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-vercel-blob)
Follow the docs to configure any one of these storage providers. For local development, it might be handy to simply store uploads on your own computer, and then when it comes to production, simply enable the plugin for the cloud storage vendor of your choice.
## Docker
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. If you don't want to have a DB connection and your build requires that, learn [here](./building-without-a-db-connection) how to prevent that.
In your Next.js config, set the `output` property `standalone`.
```js
// next.config.js
const nextConfig = {
output: 'standalone',
}
```
Dockerfile
```dockerfile
# Dockerfile
# From https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
```
## Docker Compose
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 "corepack enable && corepack prepare pnpm@latest --activate && pnpm install && pnpm dev"
depends_on:
- mongo
# - postgres
env_file:
- .env
# Ensure your DATABASE_URI uses 'mongo' as the hostname ie. mongodb://mongo/my-db-name
mongo:
image: mongo:latest
ports:
- '27017:27017'
command:
- --storageEngine=wiredTiger
volumes:
- data:/data/db
logging:
driver: none
# Uncomment the following to use postgres
# postgres:
# restart: always
# image: postgres:latest
# volumes:
# - pgdata:/var/lib/postgresql/data
# ports:
# - "5432:5432"
volumes:
data:
# pgdata:
node_modules:
```
# Preventing Production API Abuse
Source: https://payloadcms.com/docs/production/preventing-abuse
## 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.
## 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](../authentication/cookies#csrf-attacks).
## 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](../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 to specify complexity limits. These limits are based on a complexity score 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 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](../authentication/email#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](../hooks/collections) or have antivirus software in place.
# Performance
Source: https://payloadcms.com/docs/performance/overview
Payload is designed with performance in mind, but its customizability means that there are many ways to configure your app that can impact performance.
With this in mind, Payload provides several options and best practices to help you optimize your app's specific performance needs. This includes the database, APIs, and Admin Panel.
Whether you're building an app or troubleshooting an existing one, follow these guidelines to ensure that it runs as quickly and efficiently as possible.
## Building your application
### Database proximity
The proximity of your database to your server can significantly impact performance. Ensure that your database is hosted in the same region as your server to minimize latency and improve response times.
### Indexing your fields
If a particular field is queried often, build an [Index](../database/indexes) for that field to produce faster queries.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
To learn more, see the [Indexes](../database/indexes) docs.
### Querying your data
There are several ways to optimize your [Queries](../queries/overview). Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these options together as possible. This will ensure your queries are as efficient as they can be.
To learn more, see the [Query Performance](../queries/overview#performance) docs.
### Optimizing your APIs
When querying data through Payload APIs, the request lifecycle includes running hooks, access control, validations, and other operations that can add significant overhead to the request.
To optimize your APIs, any custom logic should be as efficient as possible. This includes writing lightweight hooks, preventing memory leaks, offloading long-running tasks, and optimizing custom validations.
To learn more, see the [Hooks Performance](../hooks/overview#performance) docs.
### Writing efficient validations
If your validation functions are asynchronous or computationally heavy, ensure they only run when necessary.
To learn more, see the [Validation Performance](../fields/overview#validation-performance) docs.
### Optimizing custom components
When building custom components in the Admin Panel, ensure that they are as efficient as possible. This includes using React best practices such as memoization, lazy loading, and avoiding unnecessary re-renders.
To learn more, see the [Custom Components Performance](../admin/custom-components#performance) docs.
## Other Best Practices
### Block references
Use [Block References](../fields/blocks#block-references) to share the same block across multiple fields without bloating the config. This will reduce the number of fields to traverse when processing permissions, etc. and can significantly reduce the amount of data sent from the server to the client in the Admin Panel.
For example, if you have a block that is used in multiple fields, you can define it once and reference it in each field.
To do this, use the `blockReferences` option in your blocks field:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'posts',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
{
slug: 'pages',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
],
})
```
### Using the cached Payload instance
Ensure that you do not instantiate Payload unnecessarily. Instead, Payload provides a caching mechanism to reuse the same instance across your app.
To do this, use the `getPayload` function to get the cached instance of Payload:
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
const myFunction = async () => {
const payload = await getPayload({ config })
// use payload here
}
```
### When to make direct-to-db calls
**Warning:** Direct database calls bypass all hooks and validations. Only use
this method when you are certain that the operation is safe and does not
require any of these features.
Making direct database calls can significantly improve performance by bypassing much of the request lifecycle such as hooks, validations, and other overhead associated with Payload APIs.
For example, this can be especially useful for the `update` operation, where Payload would otherwise need to make multiple API calls to fetch, update, and fetch again. Making a direct database call can reduce this to a single operation.
To do this, use the `payload.db` methods:
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: {
title: 'New Title',
},
})
```
**Note:** Direct database methods do not start a
[transaction](../database/transactions). You have to start that yourself.
#### Returning
To prevent unnecessary database computation and reduce the size of the response, you can also set `returning: false` in your direct database calls if you don't need the updated document returned to you.
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: { title: 'New Title' }, // See note above ^ about Postgres
// highlight-start
returning: false,
// highlight-end
})
```
**Note:** The `returning` option is only available on direct-to-db methods.
E.g. those on the `payload.db` object. It is not exposed to the Local API.
### Avoid bundling the entire UI library in your front-end
If your front-end imports from `@payloadcms/ui`, ensure that you do not bundle the entire package as this can significantly increase your bundle size.
To do this, import using the full path to the specific component you need:
```ts
import { Button } from '@payloadcms/ui/elements/Button'
```
Custom components within the Admin Panel, however, do not have this same restriction and can import directly from `@payloadcms/ui`:
```ts
import { Button } from '@payloadcms/ui'
```
**Tip:** Use
[`@next/bundle-analyzer`](https://nextjs.org/docs/app/guides/package-bundling)
to analyze your component tree and identify unnecessary re-renders or large
components that could be optimized.
## Optimizing local development
Everything mentioned above applies to local development as well, but there are a few additional steps you can take to optimize your local development experience.
### Enable Turbopack
**Note:** In the future this will be the default. Use at your own risk.
Add `--turbo` to your dev script to significantly speed up your local development server start time.
```json
{
"scripts": {
"dev": "next dev --turbo"
}
}
```
### Only bundle server packages in production
**Note:** This is enabled by default in `create-payload-app` since v3.28.0. If
you created your app after this version, you don't need to do anything.
By default, Next.js bundles both server and client code. However, during development, bundling certain server packages isn't necessary.
Payload has thousands of modules, slowing down compilation.
Setting this option skips bundling Payload server modules during development. Fewer files to compile means faster compilation speeds.
To do this, add the `devBundleServerPackages` option to `withPayload` in your `next.config.js` file:
```ts
const nextConfig = {
// your existing next config
}
export default withPayload(nextConfig, { devBundleServerPackages: false })
```