# Payload 3.x Documentation
> Complete official documentation for Payload 3.x. Do not mix APIs or examples from another major version.
# What is Payload?
Source: https://payloadcms.com/docs/getting-started/what-is-payload
[Video: Introduction to Payload — The open-source Next.js backend](https://www.youtube.com/watch?v=ftohATkHBi0)
## Payload is the Next.js fullstack framework.
Write a Payload Config and instantly get a full Admin Panel, a database with migrations, REST and GraphQL APIs, authentication, access control, file storage, live preview, and more — all in one open-source TypeScript codebase you own and deploy anywhere.
- [Use Cases](/docs/v3/getting-started/use-cases.md): See how teams use Payload for headless CMS, internal tools, commerce, and DAM workloads — and whether it fits yours.
- [Core Concepts](/docs/v3/getting-started/concepts.md): Get familiar with the high-level concepts Payload is built around before you write any code.
- [Installation](/docs/v3/getting-started/installation.md): Add Payload to a new or existing Next.js app and get the Admin Panel running in minutes.
- [The Admin Panel](/docs/v3/admin/overview.md): Explore the auto-generated React UI that matches the shape of your data and extends with your own components.
- [Database & Schema](/docs/v3/database/overview.md): Direct DB access and ownership with migrations, transactions, and proper indexing across MongoDB and Postgres.
- [REST & GraphQL APIs](/docs/v3/rest-api/overview.md): Instant REST, GraphQL, and straight-to-DB Node.js APIs generated from your config.
- [Authentication](/docs/v3/authentication/overview.md): A full auth system you can use in your Payload app and reuse in your own frontends.
- [Access Control](/docs/v3/access-control/overview.md): A deeply customizable access pattern that runs at the document, field, and operation level.
- [File Uploads](/docs/v3/upload/overview.md): Built-in file storage, image resizing, focal-point cropping, and media access control.
# Use Cases
Source: https://payloadcms.com/docs/getting-started/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.
> **Success**
>
> 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](/docs/v3/admin/overview.md). 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.
## Is Payload Right For You?
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
# 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](/docs/v3/configuration/overview.md): A deeply typed JavaScript object that drives everything Payload does — the central piece of every project.
- [Database](/docs/v3/database/overview.md): Plug in Postgres, MongoDB, or SQLite behind Payload's APIs via a Database Adapter — Payload is database agnostic.
- [Collections](/docs/v3/configuration/collections.md): A group of records (Documents) that share a schema. The most common way you'll model data in Payload.
- [Globals](/docs/v3/configuration/globals.md): Like Collections, but for content that has only one of — settings, navigation, footer, and so on.
- [Fields](/docs/v3/fields/overview.md): The building blocks of Payload — they define your schema and auto-generate the matching Admin UI.
- [Hooks](/docs/v3/hooks/overview.md): Run your own side effects at specific points in the Document lifecycle — before read, after create, and more.
- [Authentication](/docs/v3/authentication/overview.md): A secure, portable user-account system for both the Admin Panel and your own external applications.
- [Access Control](/docs/v3/access-control/overview.md): Decide who can read, update, or delete every Document — and what they see in the Admin Panel.
- [Admin Panel](/docs/v3/admin/overview.md): The auto-generated, fully type-safe React interface editors use to manage your data. Built on Next.js.
## Retrieving Data
Everything Payload does (create, read, update, delete, login, logout, etc.) is exposed to you via three APIs:
- [Local API](/docs/v3/local-api/overview.md): Direct-to-database access with no HTTP overhead — call payload.find from any server context, including React Server Components.
- [REST API](/docs/v3/rest-api/overview.md): Standard HTTP endpoints mounted at /api — fetch documents from any client with full pagination, depth, and sorting.
- [GraphQL](/docs/v3/graphql/overview.md): A full GraphQL API with a Playground at /api/graphql-playground. Works with graphql-request, Apollo, or any other client.
> **Note:** All of these APIs share the exact same query language. [More
> details](/docs/v3/queries/overview.md).
## 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](/docs/v3/local-api/overview.md). It executes [Access Control](/docs/v3/access-control/overview.md), [Hooks](/docs/v3/hooks/overview.md), [Validation](/docs/v3/fields/overview.md#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](/docs/v3/rest-api/overview.md) and [GraphQL API](/docs/v3/graphql/overview.md).
`@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 2+ - pnpm is preferred, yarn 1.x is not supported)
- Node.js version `^22.23.4 || >=24.22.1`
- Next.js (one of the following version ranges):
- `15.2.9` - `15.2.x`
- `15.3.9` - `15.3.x`
- `15.4.11` - `15.4.x`
- `16.2.6`+
- Any [compatible database](/docs/v3/database/overview.md) (MongoDB, Postgres or SQLite)
> **Important:** Before proceeding any further, please ensure that you have the
> above requirements met. Not all Next.js 15/16 releases are compatible — make
> sure you're using one of the supported version ranges listed above.
> **Cache Components:** While Next.js `cacheComponents` can be enabled alongside
> Payload without causing errors in the admin panel, full compatibility is not
> guaranteed. See this [GitHub pull
> request](https://github.com/payloadcms/payload/pull/16020) for the latest
> status.
## 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:
```bash
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](/docs/v3/configuration/overview.md).
## 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.
#### 1. Install the relevant packages
First, you'll want to add the required Payload packages to your project:
```bash
pnpm i payload @payloadcms/next
```
You'll also likely want to install the following optional packages:
- `@payloadcms/richtext-lexical` - Rich text editor (not needed if you don't use Rich Text)
- `sharp` - Image resizing, cropping, and focal point support (only needed if you use [Upload](/docs/v3/upload/overview.md) collections with image manipulation)
- `graphql` - Only needed if you want to use the [GraphQL API](/docs/v3/graphql/overview.md)
```bash
pnpm i @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](/docs/v3/database/overview.md). 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](/docs/v3/database/mongodb.md), run:
```bash
pnpm i @payloadcms/db-mongodb
```
- To install the [Postgres Adapter](/docs/v3/database/postgres.md), run:
```bash
pnpm i @payloadcms/db-postgres
```
- To install the [SQLite Adapter](/docs/v3/database/sqlite.md), run:
```bash
pnpm i @payloadcms/db-sqlite
```
> **Note:** New [Database Adapters](/docs/v3/database/overview.md) 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/3.x/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](/docs/v3/admin/overview.md#project-structure)._
> **Warning**
>
> 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](/docs/v3/admin/overview.md).
#### 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
}
// 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](/docs/v3/configuration/overview.md). 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_URL || '',
}),
// If you want to resize images, crop, set focal point, etc.
// make sure to install it and pass it to the config.
// This is optional - if you don't need to do these things,
// you don't need it!
sharp,
})
```
Although this is just the bare minimum config, there are _many_ more options that you can control here. To reference the full config and all of its options, [click here](/docs/v3/configuration/overview.md).
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!
# 2.0 to 3.0 Migration Guide
Source: https://payloadcms.com/docs/migration-guide/overview
# Payload 2.0 to 3.0 Migration Guide
Payload 3.0 completely replatforms the Admin Panel from a React Router single-page application onto the Next.js App Router with full support for React Server Components. This change completely separates Payload "core" from its rendering and HTTP layers, making it truly Node-safe and portable.
If you are upgrading from a _previous beta_, please see the [Upgrade From Previous Beta](#upgrade-from-previous-beta) section.
## What has changed?
The core logic and principles of Payload remain the same from 2.0 to 3.0, with the majority of changes affecting specifically the HTTP layer and the Admin Panel, which is now built upon Next.js. With this change, your entire application can be served within a single repo, with Payload endpoints are now opened within your own Next.js application, directly alongside your frontend. Payload is still headless, you will still be able to leverage it completely headlessly just as you do now with Sveltekit, etc. All Payload APIs remain exactly the same (with a few new features), and the Payload Config is generally the same, with the breaking changes detailed below.
### Table of Contents
All breaking changes are listed below. If you encounter changes that are not explicitly listed here, please consider contributing to this documentation by submitting a PR.
- [Installation](#installation)
- [Breaking Changes](#breaking-changes)
- [Custom Components](#custom-components)
- [Endpoints](#endpoints)
- [React Hooks](#react-hooks)
- [Types](#types)
- [Email Adapters](#email-adapters)
- [Plugins](#plugins)
- [Upgrade From Previous Beta](#upgrade-from-previous-beta)
## Installation
Payload 3.0 requires a set of auto-generated files that you will need to bring into your existing project. The easiest way of acquiring these is by initializing a new project via `create-payload-app`, then replace the provided Payload Config with your own.
```bash
npx create-payload-app
```
For more details, see the [Documentation](/docs/v3/getting-started/installation.md).
1. **Install new dependencies of Payload, Next.js and React**:
Refer to the package.json file made in the create-payload-app, including peerDependencies, devDependencies, and dependencies. The core package and plugins require all versions to be synced. Previously, on 2.x it was possible to be running the latest version of Payload 2.x with an older version of db-mongodb for example. This is no longer the case.
```bash
pnpm i next react react-dom payload @payloadcms/ui @payloadcms/next
```
Also install the other @payloadcms packages specific to the plugins and adapters you are using. Depending on your project, these may include:
- @payloadcms/db-mongodb
- @payloadcms/db-postgres
- @payloadcms/richtext-slate
- @payloadcms/richtext-lexical
- @payloadcms/plugin-form-builder
- @payloadcms/plugin-nested-docs
- @payloadcms/plugin-redirects
- @payloadcms/plugin-relationship
- @payloadcms/plugin-search
- @payloadcms/plugin-sentry
- @payloadcms/plugin-seo
- @payloadcms/plugin-stripe
- @payloadcms/plugin-cloud-storage - Read [More](#@payloadcms/plugin-cloud-storage).
1. Uninstall deprecated packages:
```bash
pnpm remove express nodemon @payloadcms/bundler-webpack @payloadcms/bundler-vite
```
1. Database Adapter Migrations
_If you have existing data_ and are using the MongoDB or Postgres adapters, you will need to run the database migrations to ensure your database schema is up-to-date.
- [postgres](https://github.com/payloadcms/payload/releases/tag/v3.0.0-beta.39)
- [mongodb](https://github.com/payloadcms/payload/releases/tag/v3.0.0-beta.131)
1. For Payload Cloud users, the plugin has changed.
Uninstall the old package:
```bash
pnpm remove @payloadcms/plugin-cloud
```
Install the new package:
```bash
pnpm i @payloadcms/payload-cloud
```
```diff
// payload.config.ts
- import { payloadCloud } from '@payloadcms/plugin-cloud'
+ import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
buildConfig({
// ...
plugins: [
- payloadCloud()
+ payloadCloudPlugin()
]
})
```
1. **Optional** sharp dependency
If you have upload enabled collections that use `formatOptions`, `imageSizes`, or `resizeOptions`—payload expects to have `sharp` installed. In 2.0 this was a dependency was installed for you. Now it is only installed if needed. If you have any of these options set, you will need to install `sharp` and add it to your payload.config.ts:
```bash
pnpm i sharp
```
```diff
// payload.config.ts
import sharp from 'sharp'
buildConfig({
// ...
+ sharp,
})
```
## Breaking Changes
1. Delete the `admin.bundler` property from your Payload Config. Payload no longer bundles the Admin Panel. Instead, we rely directly on Next.js for bundling.
```diff
// payload.config.ts
- import { webpackBundler } from '@payloadcms/bundler-webpack'
buildConfig({
// ...
admin: {
// ...
- bundler: webpackBundler(),
}
})
```
This also means that the `@payloadcms/bundler-webpack` and `@payloadcms/bundler-vite` packages have been deprecated. You can completely uninstall those from your project by removing them from your `package.json` file and re-running your package manager’s installation process, i.e. `pnpm i`.
1. Add the `secret` property to your Payload Config. This used to be set in the `payload.init()` function of your `server.ts` file. Instead, move it to `payload.config.ts`:
```diff
// payload.config.ts
buildConfig({
// ...
+ secret: process.env.PAYLOAD_SECRET
})
```
1. Environment variables prefixed with `PAYLOAD_PUBLIC` will no longer be available on the client. In order to access them on the client, those will now have to be prefixed with `NEXT_PUBLIC` instead.
```diff
'use client'
- const var = process.env.PAYLOAD_PUBLIC_MY_ENV_VAR
+ const var = process.env.NEXT_PUBLIC_MY_ENV_VAR
```
For more details, see the [Documentation](/docs/v3/configuration/environment-vars.md).
1. The `req` object used to extend the [Express Request](https://expressjs.com/), but now extends the [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). You may need to update your code accordingly to reflect this change. For example:
```diff
- req.headers['content-type']
+ req.headers.get('content-type')
```
1. The `admin.css` and `admin.scss` properties in the Payload Config have been removed.
```diff
// payload.config.ts
buildConfig({
// ...
admin: {
// ...
- css: '',
- scss: ''
}
})
```
To migrate, choose one of the following options:
1. For most use cases, you can simply customize the file located at `(payload)/custom.scss`. You can import or add your own styles here, such as for Tailwind.
1. For plugins author, you can use a Custom Provider at `admin.components.providers` to import your stylesheet:
```tsx
// payload.config.js
//...
admin: {
components: {
providers: [
MyProvider: './providers/MyProvider.tsx'
]
}
},
//...
// providers/MyProvider.tsx
'use client'
import React from 'react'
import './globals.css'
export const MyProvider: React.FC<{children?: any}= ({ children }) ={
return (
{children}
)
}
```
1. The `admin.indexHTML` property has been removed. Delete this from your Payload Config.
```diff
// payload.config.ts
buildConfig({
// ...
admin: {
// ...
- indexHTML: ''
}
})
```
1. The `collection.admin.hooks` property has been removed. Instead, use the new `beforeDuplicate` field-level hook which take the usual field hook arguments.
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
admin: {
hooks: {
- beforeDuplicate: ({ data }) => {
- return {
- ...data,
- title: `${data.title}-duplicate`
- }
- }
}
},
fields: [
{
name: 'title',
type: 'text',
hooks: {
+ beforeDuplicate: [
+ ({ data }) => `${data.title}-duplicate`
+ ],
},
},
],
}
```
1. Fields with `unique: true` now automatically be appended with “- Copy” through the new `admin.beforeDuplicate` field hooks (see previous bullet).
1. The `upload.staticDir` property must now be an absolute path. Before it would attempt to use the location of the Payload Config and merge the relative path set for staticDir.
```diff
// collections/Media.ts
import type { CollectionConfig } from 'payload'
import path from 'path'
+ import { fileURLToPath } from 'url'
+ const filename = fileURLToPath(import.meta.url)
+ const dirname = path.dirname(filename)
export const MediaCollection: CollectionConfig = {
slug: 'media',
upload: {
- staticDir: path.resolve(__dirname, './uploads'),
+ staticDir: path.resolve(dirname, '../uploads'),
},
}
```
1. The `upload.staticURL` property has been removed. If you were using this format URLs when using an external provider, you can leverage the `generateFileURL` functions in order to do the same.
```diff
// collections/Media.ts
import type { CollectionConfig } from 'payload'
export const MediaCollection: CollectionConfig = {
slug: 'media',
upload: {
- staticURL: '',
},
}
```
1. The `admin.favicon` property is now `admin.icons` and the types have changed:
```diff
// payload.config.ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
- favicon: 'path-to-favicon.svg',
+ icons: [{
+ path: 'path-to-favicon.svg',
+ sizes: '32x32'
+ }]
}
})
```
For more details, see the [Documentation](/docs/v3/admin/metadata.md#icons).
1. The `admin.meta.ogImage` property has been replaced by `admin.meta.openGraph.images`:
```diff
// payload.config.ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
meta: {
- ogImage: '',
+ openGraph: {
+ images: []
+ }
}
}
})
```
For more details, see the [Documentation](/docs/v3/admin/metadata.md#open-graph).
1. The args of the `admin.livePreview.url` function have changed. It no longer receives `documentInfo` as an arg, and instead, now has `collectionConfig` and `globalConfig`.
```diff
// payload.config.ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
livePreview: ({
- documentInfo,
+ collectionConfig,
+ globalConfig
}) => ''
}
})
```
1. The `admin.logoutRoute` and `admin.inactivityRoute` properties have been consolidated into a single `admin.routes` property. To migrate, simply move those two keys as follows:
```diff
// payload.config.ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
- logoutRoute: '/custom-logout',
+ inactivityRoute: '/custom-inactivity'
+ routes: {
+ logout: '/custom-logout',
+ inactivity: '/custom-inactivity'
+ }
}
})
```
1. The `custom` property in the Payload Config, i.e. Collections, Globals, and Fields is now **server only** and will **not** appear in the client-side bundle. To add custom properties to the client bundle, use the new `admin.custom` property, which will be available on _both_ the server and the client.
```diff
// payload.config.ts
import { buildConfig } from 'payload'
export default buildConfig({
custom: {
someProperty: 'My Server Prop' // Now server only!
},
admin: {
+ custom: {
+ name: 'My Client Prop' // Available in server AND client
+ }
},
})
```
1. `hooks.afterError` is now an array of functions instead of a single function. The args have also been expanded. Read [More](/docs/v3/hooks/overview.md#root-hooks).
```diff
// payload.config.ts
import { buildConfig } from 'payload'
export default buildConfig({
hooks: {
- afterError: async ({ error }) => {
+ afterError: [
+ async ({ error, req, res }) => {
+ // ...
+ }
+ ]
}
})
```
1. The `./src/public` directory is now located directly at root level `./public` [see Next.js docs for details](https://nextjs.org/docs/pages/building-your-application/optimizing/static-assets)
1. Payload now automatically removes `localized: true` property from sub-fields if a parent is localized, as it's redundant and unnecessary. If you have some existing data in this structure and you want to disable that behavior, you need to enable `allowLocalizedWithinLocalized` flag in your payload.config [read more in documentation](/docs/v3/configuration/overview.md#compatibility-flags), or create a migration script that aligns your data.
Mongodb example for a link in a page layout.
```diff
- layout.columns.en.link.en.type.en
+ layout.columns.en.link.type
```
## Custom Components
1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths:
```diff
- import { TextField, useField, etc. } from 'payload'
+ import { TextField, useField, etc. } from '@payloadcms/ui'
```
_Note: for brevity, not *all* modules are listed here_
1. All Custom Components are now defined as _file paths_ instead of direct imports. If you are using Custom Components in your Payload Config, remove the imported module and point to the file's path instead:
```diff
import { buildConfig } from 'payload'
- import { MyComponent } from './src/components/Logout'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
- Button: MyComponent,
+ Button: '/src/components/Logout#MyComponent'
}
}
},
})
```
For more details, see the [Documentation](/docs/v3/custom-components/overview.md#component-paths).
1. All Custom Components are now server-rendered by default, and therefore, cannot use state or hooks directly. If you’re using Custom Components in your app that requires state or hooks, add the `'use client'` directive at the top of the file.
```diff
// components/MyClientComponent.tsx
+ 'use client'
import React, { useState } from 'react'
export const MyClientComponent = () => {
const [state, setState] = useState()
return (
{state}
)
}
```
For more details, see the [Documentation](/docs/v3/custom-components/overview.md#client-components).
1. The `admin.description` property within Collection, Globals, and Fields no longer accepts a React Component. Instead, you must define it as a Custom Component.
1. For Collections, use the `admin.components.edit.Description` key:
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
- import { MyCustomDescription } from '../components/MyCustomDescription'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
admin: {
- description: MyCustomDescription,
+ components: {
+ edit: {
+ Description: 'path/to/MyCustomDescription'
+ }
+ }
}
}
```
2. For Globals, use the `admin.components.elements.Description` key:
```diff
// globals/Site.ts
import type { GlobalConfig } from 'payload'
- import { MyCustomDescription } from '../components/MyCustomDescription'
export const SiteGlobal: GlobalConfig = {
slug: 'site',
admin: {
- description: MyCustomDescription,
+ components: {
+ elements: {
+ Description: 'path/to/MyCustomDescription'
+ }
+ }
}
}
```
3. For Fields, use the `admin.components.Description` key:
```diff
// fields/MyField.ts
import type { FieldConfig } from 'payload'
- import { MyCustomDescription } from '../components/MyCustomDescription'
export const MyField: FieldConfig = {
type: 'text',
admin: {
- description: MyCustomDescription,
+ components: {
+ Description: 'path/to/MyCustomDescription'
+ }
}
}
```
1. Array Field row labels and Collapsible Field label now _only_ accepts a React Component, and no longer accepts a plain string or record:
```diff
// file: Collection.tsx
import type { CollectionConfig } from 'payload'
- import { MyCustomRowLabel } from './components/MyCustomRowLabel.tsx'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
fields: [
{
name: 'my-array',
type: 'array',
admin: {
components: {
- RowLabel: 'My Array Row Label,
+ RowLabel: './components/RowLabel.ts'
}
},
fields: [...]
},
{
name: 'my-collapsible',
type: 'collapsible',
admin: {
components: {
- Label: 'My Collapsible Label',
+ Label: './components/RowLabel.ts'
}
},
fields: [...]
}
]
}
```
1. All default view keys are now camelcase:
For example, for Root Views:
```diff
// file: payload.config.ts
import { buildConfig } from 'payload'
export default buildConfig({
admin: {
views: {
- Account: ...
+ account: ...
}
})
```
Or Document Views:
```diff
// file: Collection.tsx
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
views: {
- Edit: {
- Default: ...
- }
+ edit: {
+ default: ...
+ }
}
}
}
```
1. Custom Views within the config no longer accept React Components directly, instead, you must use their `Component` property:
```diff
// file: Collection.tsx
import type { CollectionConfig } from 'payload'
- import { MyCustomView } from './components/MyCustomView.tsx'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
views: {
- Edit: MyCustomView
+ edit: {
+ Component: './components/MyCustomView.tsx'
+ }
}
}
}
```
This also means that Custom Root Views are no longer defined on the `edit` key. Instead, use the new `views.root` key:
```diff
// file: Collection.tsx
import type { CollectionConfig } from 'payload'
- import { MyCustomRootView } from './components/MyCustomRootView.tsx'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
views: {
- Edit: MyCustomRootView
edit: {
+ root: {
+ Component: './components/MyCustomRootView.tsx'
+ }
}
}
}
}
```
1. The `href` and `isActive` functions on View Tabs no longer includes the `match` or `location` arguments. This is is a property specific to React Router, not Next.js. If you need to do URL matching similar to this, use a custom tab that fires of some hooks, i.e. `usePathname()` and run it through your own utility functions:
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
admin: {
components: {
views: {
- Edit: {
- Tab: {
- isActive: ({ href, location, match }) => true,
- href: ({ href, location, match }) => ''
- },
- },
+ edit: {
+ tab: {
+ isActive: ({ href }) => true,
+ href: ({ href }) => '' // or use a Custom Component (see below)
+ // Component: './path/to/CustomComponent.tsx'
+ }
+ },
},
},
},
}
```
1. The `admin.components.views[key].Tab.pillLabel` has been replaced with `admin.components.views[key].tab.Pill`:
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
admin: {
components: {
- views: {
- Edit: {
- Tab: {
- pillLabel: 'Hello, world!',
- },
- },
+ edit: {
+ tab: {
+ Pill: './path/to/CustomPill.tsx',
+ }
+ },
},
},
},
}
```
1. `react-i18n` was removed, the `Trans` component from `react-i18n` has been replaced with a Payload-provided solution:
```diff
'use client'
- import { Trans } from "react-i18n"
+ import { Translation } from "@payloadcms/ui"
// Example string to translate:
// "loggedInChangePassword": "To change your password, go to your <0>account0> and edit your password there."
export const MyComponent = () => {
return (
-
- account
-
+ ,
+ }}
+ />
)
}
```
## Endpoints
1. All endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.:
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
endpoints: [
- {
- path: '/whoami/:parameter',
- method: 'post',
- handler: (req, res) => {
- res.json({
- parameter: req.params.parameter,
- name: req.body.name,
- age: req.body.age,
- })
- }
- },
+ {
+ path: '/whoami/:parameter',
+ method: 'post',
+ handler: (req) => {
+ return Response.json({
+ parameter: req.routeParams.parameter,
+ // ^^ `params` is now `routeParams`
+ name: req.data.name,
+ age: req.data.age,
+ })
+ }
+ }
]
}
```
1. Endpoint handlers no longer resolves `data`, `locale`, or `fallbackLocale` for you on the request. Instead, you must resolve them yourself or use the Payload-provided utilities:
```diff
// collections/Posts.ts
import type { CollectionConfig } from 'payload'
+ import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
+ import { addLocalesToRequest } from '@payloadcms/next/utilities'
export const PostsCollection: CollectionConfig = {
slug: 'posts',
endpoints: [
- {
- path: '/whoami/:parameter',
- method: 'post',
- handler: async (req) => {
- return Response.json({
- name: req.data.name, // data will be undefined
- })
- }
- },
+ {
+ path: '/whoami/:parameter',
+ method: 'post',
+ handler: async (req) => {
+ // mutates req, must be awaited
+ await addDataAndFileToRequest(req)
+ await addLocalesToRequest(req)
+
+ return Response.json({
+ name: req.data.name, // data is now available
+ fallbackLocale: req.fallbackLocale,
+ locale: req.locale,
+ })
+ }
+ }
]
}
```
## React Hooks
1. The `useTitle` hook has been consolidated into the `useDocumentInfo` hook. Instead, you can get title directly from document info context:
```diff
'use client'
- import { useTitle } from 'payload'
+ import { useDocumentInfo } from '@payloadcms/ui'
export const MyComponent = () => {
- const title = useTitle()
+ const { title } = useDocumentInfo()
// ...
}
```
1. The `useDocumentInfo` hook no longer returns `collection` or `global`. Instead, various properties of the config are passed, like `collectionSlug` and `globalSlug`. You can use these to access a client-side config, if needed, through the `useConfig` hook (see next bullet).
```diff
'use client'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyComponent = () => {
const {
- collection,
- global,
+ collectionSlug,
+ globalSlug
} = useDocumentInfo()
// ...
}
```
1. The `useConfig` hook now returns a `ClientConfig` and not a `SanitizedConfig`. This is because the config itself is not serializable and so it is not able to be thread through to the client. This means that all non-serializable props have been omitted from the Client Config, such as `db`, `bundler`, etc.
```diff
'use client'
- import { useConfig } from 'payload'
+ import { useConfig } from '@payloadcms/ui'
export const MyComponent = () => {
- const config = useConfig() // used to be a 'SanitizedConfig'
+ const { config } = useConfig() // now is a 'ClientConfig'
// ...
}
```
For more details, see the [Documentation](../admin/custom-components/overview#accessing-the-payload-config).
1. The `useCollapsible` hook has had slight changes to its property names. `collapsed` is now `isCollapsed` and `withinCollapsible` is now `isWithinCollapsible`.
```diff
'use client'
import { useCollapsible } from '@payloadcms/ui'
export const MyComponent = () => {
- const { collapsed, withinCollapsible } = useCollapsible()
+ const { isCollapsed, isWithinCollapsible } = useCollapsible()
}
```
1. The `useTranslation` hook no longer takes any options, any translations using shorthand accessors will need to use the entire `group:key`
```diff
'use client'
- import { useTranslation } from 'payload'
+ import { useTranslation } from '@payloadcms/ui'
export const MyComponent = () => {
- const { i18n, t } = useTranslation('general')
+ const { i18n, t } = useTranslation()
- return
{t('cancel')}
+ return
{t('general:cancel')}
}
```
## Types
1. The `Fields` type was renamed to `FormState` for improved semantics. If you were previously importing this type in your own application, simply change the import name:
```diff
- import type { Fields } from 'payload'
+ import type { FormState } from 'payload'
```
1. The `BlockField` and related types have been renamed to `BlocksField` for semantic accuracy.
```diff
- import type { BlockField, BlockFieldProps } from 'payload'
+ import type { BlocksField, BlocksFieldProps } from 'payload'
```
## Email Adapters
Email functionality has been abstracted out into email adapters.
- All existing nodemailer functionality was abstracted into the `@payloadcms/email-nodemailer` package
- No longer configured with ethereal.email by default.
- Ability to pass email into the `init` function has been removed.
- Warning will be given on startup if email not configured. Any `sendEmail` call will simply log the To address and subject.
- A Resend adapter is now also available via the `@payloadcms/email-resend` package.
### If you used the default email configuration in 2.0 (nodemailer):
```tsx
// ❌ Before
// via payload.init
payload.init({
email: {
transport: someNodemailerTransport
fromName: 'hello',
fromAddress: 'hello@example.com',
},
})
// or via email in payload.config.ts
export default buildConfig({
email: {
transport: someNodemailerTransport
fromName: 'hello',
fromAddress: 'hello@example.com',
},
})
// ✅ After
// Using new nodemailer adapter package
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
export default buildConfig({
email: nodemailerAdapter() // This will be the old ethereal.email functionality
})
// or pass in transport
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',
defaultFromName: 'Payload',
transport: await nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
})
})
```
### Removal of rate-limiting
- Now only available if using custom server and using express or similar
## Plugins
1. _All_ plugins have been standardized to use _named exports_ (as opposed to default exports). Most also have a suffix of `Plugin` to make it clear what is being imported.
```diff
- import seo from '@payloadcms/plugin-seo'
+ import { seoPlugin } from '@payloadcms/plugin-seo'
- import stripePlugin from '@payloadcms/plugin-stripe'
+ import { stripePlugin } from '@payloadcms/plugin-stripe'
// and so on for every plugin
```
## `@payloadcms/plugin-cloud-storage`
- The adapters that are exported from `@payloadcms/plugin-cloud-storage` (ie. `@payloadcms/plugin-cloud-storage/s3`) package have been removed.
- New _standalone_ packages have been created for each of the existing adapters. Please see the documentation for the one that you use.
- `@payloadcms/plugin-cloud-storage` is still fully supported but should only to be used if you are providing a custom adapter that does not have a dedicated package.
- If you have created a custom adapter, the type must now provide a `name` property.
| Service | Package |
| -------------------- | ---------------------------------------------------------------------------- |
| Vercel Blob | https://github.com/payloadcms/payload/tree/3.x/packages/storage-vercel-blob |
| AWS S3 | https://github.com/payloadcms/payload/tree/3.x/packages/storage-s3 |
| Azure | https://github.com/payloadcms/payload/tree/3.x/packages/storage-azure |
| Google Cloud Storage | https://github.com/payloadcms/payload/tree/3.x/packages/storage-gcs |
```tsx
// ❌ Before (required peer dependencies depending on adapter)
import { cloudStorage } from '@payloadcms/plugin-cloud-storage'
import { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3'
plugins: [
cloudStorage({
collections: {
media: {
adapter: s3Adapter({
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,
},
}),
},
},
}),
],
// ✅ After
import { s3Storage } from '@payloadcms/storage-s3'
plugins: [
s3Storage({
collections: {
media: true,
},
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,
},
}),
],
```
## `@payloadcms/plugin-form-builder`
1. Field overrides for form and form submission collections now accept a function with a `defaultFields` inside the args instead of an array of config
```diff
// payload.config.ts
import { buildConfig } from 'payload'
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
const config = buildConfig({
// ...
plugins: formBuilderPlugin({
- fields: [
- {
- name: 'custom',
- type: 'text',
- }
- ],
+ fields: ({ defaultFields }) => {
+ return [
+ ...defaultFields,
+ {
+ name: 'custom',
+ type: 'text',
+ },
+ ]
+ }
})
})
```
## `@payloadcms/plugin-redirects`
1. Field overrides for the redirects collection now accepts a function with a `defaultFields` inside the args instead of an array of config
```diff
// payload.config.ts
import { buildConfig } from 'payload'
import { redirectsPlugin } from '@payloadcms/plugin-redirects'
const config = buildConfig({
// ...
plugins: redirectsPlugin({
- fields: [
- {
- name: 'custom',
- type: 'text',
- }
- ],
+ fields: ({ defaultFields }) => {
+ return [
+ ...defaultFields,
+ {
+ name: 'custom',
+ type: 'text',
+ },
+ ]
+ }
})
})
```
## `@payloadcms/richtext-lexical`
If you have custom features for `@payloadcms/richtext-lexical` you will need to migrate your code to the new API. Read more about the new API in the [documentation](/docs/v3/rich-text/building-custom-features.md).
## Reserved Field names
Payload reserves certain field names for internal use. Using any of the following names in your collections or globals will result in those fields being sanitized from the config, which can cause deployment errors. Ensure that any conflicting fields are renamed before migrating.
### General Reserved Names
- `file`
- `_id` (MongoDB only)
- `__v` (MongoDB only)
**Important Note**: It is recommended to avoid using field names with an underscore (`_`) prefix unless explicitly required by a plugin. Payload uses this prefix for internal columns, which can lead to conflicts in certain SQL conditions. The following are examples of reserved internal columns (this list is not exhaustive and other internal fields may also apply):
- `_order`
- `_path`
- `_uuid`
- `_parent_id`
- `_locale`
### Auth-Related Reserved Names
These are restricted if your collection uses `auth: true` and does not have `disableAuthStrategy: true`:
- `salt`
- `hash`
- `apiKey` (when `auth.useAPIKey: true` is enabled)
- `useAPIKey` (when `auth.useAPIKey: true` is enabled)
- `resetPasswordToken`
- `resetPasswordExpiration`
- `password`
- `email`
- `username`
### Upload-Related Reserved Names
These apply if your collection has `upload: true` configured:
- `filename`
- `mimetype`
- `filesize`
- `width`
- `height`
- `focalX`
- `focalY`
- `url`
- `thumbnailURL`
If `imageSizes` is configured, the following are also reserved:
- `sizes`
If any of these names are found in your collection / global fields, update them before migrating to avoid unexpected issues.
## Upgrade from previous beta
Reference this [community-made site](https://payload-releases-filter.vercel.app/?version=3&from=152429656&to=188243150&sort=asc&breaking=on). Set your version, sort by oldest first, enable breaking changes only.
Then go through each one of the breaking changes and make the adjustments. You can optionally reference the [blank template](https://github.com/payloadcms/payload/tree/3.x/templates/blank) for how things should end up.
# 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](/docs/v3/database/overview.md) choice to the appearance of the [Admin Panel](/docs/v3/admin/overview.md) is fully controlled through the Payload Config. From here you can define [Fields](/docs/v3/fields/overview.md), add [Localization](/docs/v3/configuration/localization.md), enable [Authentication](/docs/v3/authentication/overview.md), configure [Access Control](/docs/v3/access-control/overview.md), 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](/docs/v3/database/overview.md) you'd like to use, then use [Collections](/docs/v3/configuration/collections.md) or [Globals](/docs/v3/configuration/globals.md) to define the schema of your data through [Fields](/docs/v3/fields/overview.md).
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_URL,
}),
collections: [
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
},
],
},
],
})
```
> **Note:** For more complex examples, see the
> [Templates](https://github.com/payloadcms/payload/tree/3.x/templates) and
> [Examples](https://github.com/payloadcms/payload/tree/3.x/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](/docs/v3/admin/overview.md#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](/docs/v3/rich-text/overview.md). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](/docs/v3/database/overview.md). |
| **`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](/docs/v3/configuration/collections.md). |
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
| **`globals`** | An array of Globals for Payload to manage. [More details](/docs/v3/configuration/globals.md). |
| **`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](/docs/v3/configuration/localization.md). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](#logger). |
| **`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](/docs/v3/graphql/overview.md#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](/docs/v3/authentication/cookies.md#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](/docs/v3/queries/depth.md). |
| **`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](/docs/v3/folders/overview.md). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](/docs/v3/query-presets/overview.md). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](/docs/v3/queries/depth.md). |
| **`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](/docs/v3/upload/overview.md#payload-wide-upload-options). |
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](/docs/v3/admin/overview.md#root-level-routes). |
| **`email`** | Configure the Email Adapter for Payload to use. [More details](/docs/v3/email/overview.md). |
| **`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](/docs/v3/hooks/overview.md). |
| **`plugins`** | An array of Plugins. [More details](/docs/v3/plugins/overview.md). |
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](/docs/v3/rest-api/overview.md#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](/docs/v3/configuration/i18n.md). |
| **`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](/docs/v3/custom-components/overview.md#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](/docs/v3/configuration/collections.md) and [Globals](/docs/v3/configuration/globals.md), and to ensure that Payload uses your [Generated Types](/docs/v3/typescript/overview.md) for all [Local API](/docs/v3/local-api/overview.md) 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](/docs/v3/typescript/overview.md). |
| **`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. |
| **`strictDraftTypes`** | Enable strict type safety for draft mode. When enabled: (1) Query operations (`find`, `findByID`) with `draft: true` will type required fields as optional, since validation is skipped for drafts. (2) The `draft` property is forbidden for collections without drafts in create operations. (3) Create operations enforce proper data requirements via discriminated unions. Defaults to `false`. **This will become the default behavior in v4.0.** |
## 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](/docs/v3/configuration/environment-vars.md) 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.
## Logger
Payload uses [Pino](https://getpino.io) as its logger. By default, Payload logs pretty-printed output to stdout. You can customise this through the `logger` option in your config.
The `logger` option accepts one of three forms:
### 1. Logger options with an optional destination stream
Pass a `{ options, destination? }` object to configure the logger. `options` accepts standard [Pino logger options](https://getpino.io/#/docs/api?id=options) such as `level` and `name`. An optional `destination` stream can be supplied as a second argument.
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// highlight-start
logger: {
options: {
level: 'debug',
},
},
// highlight-end
})
```
To write logs to a file, pass a destination stream:
```ts
import { buildConfig } from 'payload'
import { pino } from 'pino'
export default buildConfig({
// highlight-start
logger: {
options: { level: 'info' },
destination: pino.destination('/var/log/payload.log'),
},
// highlight-end
})
```
> **Compatibility note:** The `transport` property of Pino's `LoggerOptions` may
> fail with `"unable to determine transport target"` in some environments. Pino
> transports spawn a worker thread with their own module resolution, which can
> break when modules are bundled or the project uses ESM. If you encounter this
> error, use a pre-instantiated logger instead (see below).
### 2. Pre-instantiated logger
Pass a fully configured Pino logger instance. This is the recommended approach when you need custom transports, formatters, or `pino-pretty` in development:
```ts
import { buildConfig } from 'payload'
import { pino } from 'pino'
import pinoPretty from 'pino-pretty'
const logger = pino({ level: 'debug' }, pinoPretty({ colorize: true }))
export default buildConfig({
// highlight-start
logger,
// highlight-end
})
```
### 3. Disabling logging
Set the `DISABLE_LOGGING` environment variable to `'true'` to suppress all log output:
```sh
DISABLE_LOGGING=true node server.js
```
## 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](/docs/v3/database/overview.md) based on the [Fields](/docs/v3/fields/overview.md) that you define, and automatically generates a [Local API](/docs/v3/local-api/overview.md), [REST API](/docs/v3/rest-api/overview.md), and [GraphQL API](/docs/v3/graphql/overview.md) used to manage your Documents.
Collections are also used to achieve [Authentication](/docs/v3/authentication/overview.md) 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](/docs/v3/access-control/overview.md), [Hooks](/docs/v3/hooks/overview.md), [Admin Options](#admin-options), and more.
To define a Collection Config, use the `collections` property in your [Payload Config](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/globals.md) instead.
## Config Options
It's often best practice to write your Collections in separate files and then import them into the main [Payload Config](/docs/v3/configuration/overview.md).
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/3.x/templates) and
> [Examples](https://github.com/payloadcms/payload/tree/3.x/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](/docs/v3/access-control/collections.md). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](/docs/v3/authentication/overview.md). |
| `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](/docs/v3/rest-api/overview.md#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](/docs/v3/fields/overview.md). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](/docs/v3/hooks/collections.md). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [orderable](#orderable) 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](/docs/v3/query-presets/overview.md). |
| `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](/docs/v3/admin/locked-documents.md). |
| `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](/docs/v3/trash/overview.md). |
| `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](/docs/v3/upload/overview.md) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](/docs/v3/versions/overview.md#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](/docs/v3/queries/select.md#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](/docs/v3/queries/select.md). |
| `disableBulkDelete` | Disable the bulk delete operation for the collection in the admin panel and the REST API |
| `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](/docs/v3/fields/overview.md) documentation.
### Access Control
[Collection Access Control](/docs/v3/access-control/collections.md) determines what a user can and cannot do with any given Document within a Collection. To learn more, go to the [Access Control](/docs/v3/access-control/overview.md) documentation.
### Hooks
[Collection Hooks](/docs/v3/hooks/collections.md) 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](/docs/v3/hooks/overview.md) documentation.
### Orderable
When `orderable` is enabled, Payload uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) to efficiently manage document order. When enabled on collections, this allows you to manually drag and drop documents in the Admin Panel to reorder them, as well as programmatically set the order of documents via the Local API, REST API, or GraphQL API.
Orderable can also be added to [joins fields](/docs/v3/fields/join.md).
#### Fractional indexing
If you need to generate order keys programmatically (e.g., when creating documents via the Local API with a specific order), you can import the utilities directly:
```ts
import { generateKeyBetween, generateNKeysBetween } from 'payload/shared'
// Generate a key between two existing keys (or null for start/end)
const newKey = generateKeyBetween('a0', 'a1')
// Generate a key at the start (before 'a0')
const startKey = generateKeyBetween(null, 'a0')
// Generate a key at the end (after 'a1')
const endKey = generateKeyBetween('a1', null)
// Generate multiple keys between two existing keys
const keys = generateNKeysBetween('a0', 'a1', 5)
```
These utilities are useful when you need fine-grained control over document ordering, such as inserting documents at specific positions or batch-creating documents with predetermined order.
## Admin Options
The behavior of Collections within the [Admin Panel](/docs/v3/admin/overview.md) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](/docs/v3/custom-components/overview.md), 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. |
| `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](/docs/v3/fields/rich-text.md) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](/docs/v3/fields/rich-text.md) 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](/docs/v3/folders/overview.md). |
| `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](/docs/v3/admin/metadata.md). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](/docs/v3/admin/preview.md). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](/docs/v3/live-preview/overview.md). |
| `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](/docs/v3/custom-components/overview.md) which only apply to Collection-specific UI within the [Admin Panel](/docs/v3/admin/overview.md). 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](/docs/v3/custom-components/list-view.md#afterlist). |
| `afterListTable` | An array of components to inject _after_ the built-in List View's table. [More details](/docs/v3/custom-components/list-view.md#afterlisttable). |
| `beforeList` | An array of components to inject _before_ the built-in List View. [More details](/docs/v3/custom-components/list-view.md#beforelist). |
| `beforeListTable` | An array of components to inject _before_ the built-in List View's table. [More details](/docs/v3/custom-components/list-view.md#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](/docs/v3/custom-components/list-view.md#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](/docs/v3/custom-components/custom-views.md). |
#### Edit View Options
Custom components within the Edit View are configured under `admin.components.edit`:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
Status: '/path/to/CustomStatus',
SaveButton: '/path/to/CustomSaveButton',
PublishButton: '/path/to/CustomPublishButton',
// highlight-end
},
},
},
}
```
The following components can be customized within `admin.components.edit`:
| Option | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](/docs/v3/custom-components/edit-view.md#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](/docs/v3/custom-components/edit-view.md#editmenuitems). |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](/docs/v3/versions/drafts.md) must be disabled. [More details](/docs/v3/custom-components/edit-view.md#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](/docs/v3/versions/drafts.md) must be enabled and autosave must be disabled. [More details](/docs/v3/custom-components/edit-view.md#savedraftbutton). |
| `Status` | Replace the default Status component within the Edit View. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#status). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#publishbutton). |
| `UnpublishButton` | Replace the default Unpublish Button within the Edit View. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#unpublishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](/docs/v3/admin/preview.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](/docs/v3/upload/overview.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#upload). |
> **Important:** All edit view components must be configured under
> `admin.components.edit`, not directly under `admin.components`.
>
> For example, use `admin.components.edit.Status`, not `admin.components.Status`.
> **Note:** For details on how to build Custom Components, see [Building Custom
> Components](/docs/v3/custom-components/overview.md#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](/docs/v3/queries/pagination.md) 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](/docs/v3/queries/select.md) 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`](/docs/v3/queries/select.md) 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](/docs/v3/types/payload-request.md) 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](/docs/v3/configuration/collections.md), 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](/docs/v3/database/overview.md) based on the [Fields](/docs/v3/fields/overview.md) that you define, and automatically generates a [Local API](/docs/v3/local-api/overview.md), [REST API](/docs/v3/rest-api/overview.md), and [GraphQL API](/docs/v3/graphql/overview.md) 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](/docs/v3/access-control/overview.md), [Hooks](/docs/v3/hooks/overview.md), [Admin Options](#admin-options), and more.
To define a Global Config, use the `globals` property in your [Payload Config](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/collections.md) instead.
## Config Options
It's often best practice to write your Globals in separate files and then import them into the main [Payload Config](/docs/v3/configuration/overview.md).
Here is what a simple Global Config might look like:
```ts
import type { 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/3.x/templates) and
> [Examples](https://github.com/payloadcms/payload/tree/3.x/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](/docs/v3/access-control/globals.md). |
| `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](/docs/v3/rest-api/overview.md#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](/docs/v3/fields/overview.md). |
| `graphQL` | Manage GraphQL-related properties related to this global. [More details](#graphql) |
| `hooks` | Entry point for Hooks. [More details](/docs/v3/hooks/globals.md). |
| `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](/docs/v3/admin/locked-documents.md). |
| `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](/docs/v3/versions/overview.md#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](/docs/v3/queries/select.md). |
_\* An asterisk denotes that a property is required._
### Fields
Fields define the schema of the Global. To learn more, go to the [Fields](/docs/v3/fields/overview.md) documentation.
### Access Control
[Global Access Control](/docs/v3/access-control/globals.md) determines what a user can and cannot do with any given Global Document. To learn more, go to the [Access Control](/docs/v3/access-control/overview.md) documentation.
### Hooks
[Global Hooks](/docs/v3/hooks/globals.md) 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](/docs/v3/hooks/overview.md) documentation.
## Admin Options
The behavior of Globals within the [Admin Panel](/docs/v3/admin/overview.md) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](/docs/v3/custom-components/overview.md), setting page metadata, and more.
To configure Admin Options for Globals, use the `admin` property in your Global Config:
```ts
import type { 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](/docs/v3/admin/preview.md). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](/docs/v3/live-preview/overview.md). |
| `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](/docs/v3/admin/metadata.md). |
### Custom Components
Globals can set their own [Custom Components](/docs/v3/custom-components/overview.md) which only apply to Global-specific UI within the [Admin Panel](/docs/v3/admin/overview.md). 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](/docs/v3/custom-components/custom-views.md). |
#### Edit View Options
Custom components within the Edit View are configured under `admin.components.elements`:
```ts
import type { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
components: {
elements: {
// highlight-start
Status: '/path/to/CustomStatus',
SaveButton: '/path/to/CustomSaveButton',
PublishButton: '/path/to/CustomPublishButton',
// highlight-end
},
},
},
}
```
The following elements can be customized within `admin.components.elements`:
| Option | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save button. [More details](/docs/v3/custom-components/edit-view.md#beforedocumentcontrols). |
| `Description` | A component to render below the Global label in the Edit View. [More details](/docs/v3/custom-components/edit-view.md#description). |
| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](/docs/v3/versions/drafts.md) must be disabled. [More details](/docs/v3/custom-components/edit-view.md#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](/docs/v3/versions/drafts.md) must be enabled and autosave must be disabled. [More details](/docs/v3/custom-components/edit-view.md#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#publishbutton). |
| `UnpublishButton` | Replace the default Unpublish Button with a Custom Component. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#unpublishbutton). |
| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](/docs/v3/admin/preview.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#previewbutton). |
| `Status` | Replace the default Status component with a Custom Component. [Drafts](/docs/v3/versions/drafts.md) must be enabled. [More details](/docs/v3/custom-components/edit-view.md#status). |
> **Note:** For details on how to build Custom Components, see [Building Custom
> Components](/docs/v3/custom-components/overview.md#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](/docs/v3/admin/overview.md) is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/3.x/packages/translations). With I18n, editors can navigate the interface and read API error messages in their preferred language. This is similar to [Localization](/docs/v3/configuration/localization.md), 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](/docs/v3/configuration/overview.md):
```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/3.x/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](/docs/v3/configuration/overview.md):
```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.
### Text direction
Payload sets the Admin Panel's text direction for you based on the user's selected language:
- **Right-to-left (RTL):** Arabic (`ar`), Persian (`fa`), and Hebrew (`he`).
- **Left-to-right (LTR):** All other supported languages.
To enable an RTL language, add it to `i18n.supportedLanguages`. There's no extra configuration needed.
Text direction follows the language used for the admin interface, independently of the locale used to edit your content.
### 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/collections.md) and [Globals](/docs/v3/configuration/globals.md), 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](/docs/v3/admin/preferences.md).
## 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](/docs/v3/custom-components/overview.md):
```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 { 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](/docs/v3/configuration/i18n.md), 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/i18n.md).
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.
## Status Localization
Payload allows you to localize the `status` field for **draft enabled** collections and globals. This lets you manage publication status independently for each locale, ensures the admin UI always shows the status for the selected locale, and unpublish content in a single locale.
> **Important:** This feature is **experimental** and currently in beta, you may
> encounter some limitations or bugs. Please test thoroughly before using in
> production.
**Two-Step Setup Required:** To enable localized status, you need to set **two** configuration options:
1. Enable the experimental flag in your main config
2. Enable it for specific collections or globals
### Step 1: Enable Experimental Flag
First, enable the experimental flag in your main Payload config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// highlight-start
experimental: {
localizeStatus: true, // Required to enable the feature globally
},
// highlight-end
// ... rest of your config
})
```
### Step 2: Enable for Collections/Globals
Then, enable it for specific collections or globals:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
versions: {
drafts: {
// highlight-start
localizeStatus: true, // Enable for this specific collection
// highlight-end
},
},
}
```
When enabled, the `status` field will be stored as an object keyed by locales:
```ts
status: {
en: 'published',
es: 'draft',
de: 'published',
}
```
`localizeStatus` is disabled by default, in which case the `status` field returns a single string (`'draft'` or `'published'`) representing the latest document status across all locales.
## Retrieving Localized Docs
When retrieving documents, you can specify which locale you'd like to receive as well as which fallback locale should be
used.
#### REST API
REST API locale functionality relies on URL query parameters.
**`?locale=`**
Specify your desired locale by providing the `locale` query parameter directly in the endpoint URL.
**`?fallback-locale=`**
Specify fallback locale to be used by providing the `fallback-locale` query parameter. This can be provided as either a
valid locale as provided to your base Payload Config, or `'null'`, `'false'`, or `'none'` to disable falling back.
**Example:**
```
fetch('https://localhost:3000/api/pages?locale=es&fallback-locale=none');
```
#### GraphQL API
In the GraphQL API, you can specify `locale` and `fallbackLocale` args to all relevant queries and mutations.
The `locale` arg will only accept valid locales, but locales will be formatted automatically as valid GraphQL enum
values (dashes or special characters will be converted to underscores, spaces will be removed, etc.). If you are curious
to see how locales are auto-formatted, you can use the [GraphQL playground](/docs/v3/graphql/overview.md#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
}
}
}
```
> **Note**
>
> 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](/docs/v3/database/overview.md) 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_URL=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](/docs/v3/admin/overview.md) 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](/docs/v3/custom-components/overview.md) 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](/docs/v3/database/mongodb.md) with [Mongoose](https://mongoosejs.com/)
- [Postgres](/docs/v3/database/postgres.md) with [Drizzle](https://drizzle.team/)
- [SQLite](/docs/v3/database/sqlite.md) with [Drizzle](https://drizzle.team/)
To configure a Database Adapter, use the `db` property in your [Payload Config](/docs/v3/configuration/overview.md):
```ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
export default buildConfig({
// ...
// highlight-start
db: mongooseAdapter({
url: process.env.DATABASE_URL,
}),
// 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](/docs/v3/configuration/localization.md)
- You leverage a lot of [Arrays](/docs/v3/fields/array.md), [Blocks](/docs/v3/fields/blocks.md), or `hasMany` [Select](/docs/v3/fields/select.md) 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](/docs/v3/database/migrations.md)
- 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](/docs/v3/configuration/localization.md), [Arrays](/docs/v3/fields/array.md), [Blocks](/docs/v3/fields/blocks.md), etc. The only thing that is not supported in SQLite yet is the [Point Field](/docs/v3/fields/point.md), 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**
>
> 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](/docs/v3/database/transactions.md) for you. All
you need to do is pass the `req` object to any [Local API](/docs/v3/local-api/overview.md) 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](/docs/v3/database/transactions.md).
```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](/docs/v3/database/postgres.md#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**
>
> 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.
> **Success**
>
> Tip: migrations created by Payload are relatively programmatic in nature, so
> there should not be any surprises, but before you check in the created
> migration it's a good idea to always double-check the contents of the
> migration files.
**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",
// 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 const 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](/docs/v3/configuration/collections.md):
```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
},
],
}
```
## Unique Fields
Setting `unique: true` on a field creates a **collection-wide** database unique index on that field's path. This means no two documents in the collection can share the same value for that field.
> **Warning**
>
> **Using `unique` on fields nested inside `array` or `blocks`** creates a
> collection-wide unique index on the dotted path (e.g. `items.key`). This is
> **not** the same as enforcing uniqueness within a single document's array rows
> — it prevents *any two documents* from having the same value at that path.
>
> Additionally, on **MongoDB**, if the nested field also has `required: true`,
> Payload creates a non-sparse unique index. Documents that do not contain the
> array will share a `null` value at that path, causing a duplicate key error
> when saving a second document without the array. Removing `required: true`
> restores the sparse index and avoids this collision.
>
> If you need to enforce uniqueness _within_ a document's array rows (e.g. no
> duplicate keys in the same document), use a custom
> [`validate`](/docs/v3/fields/overview.md#validation) function on the array field
> instead.
## 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_URL,
}),
})
```
## Options
| Option | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `bulkOperationsSingleTransaction` | When `true`, bulk update and delete operations will process documents one at a time in separate transactions instead of all at once in a single transaction. Useful for avoiding transaction limitations with large datasets in DocumentDB and Cosmos DB. Defaults to `false`. |
| `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_URL,
// 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 requires the root config property `indexSortableFields` to be set to `true`.
## Using collation
There are situations where you may want to use language-specific string comparison, for example when sorting strings with accents or in different languages. MongoDB supports this via [collation](https://www.mongodb.com/docs/manual/reference/collation/).
We thread your locale automatically through to the MongoDB queries when collation is enabled in the adapter, so that when you sort by a field, it uses the correct language rules for that locale.
To enable collation, set the `collation` option in the adapter configuration:
```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_URL,
collation: {
// Set any MongoDB collation options here.
// The locale will be set automatically based on the request locale.
strength: 1, // Example option
},
}),
})
```
> **Warning**
>
> Collation will affect things like sort based on the locale being used. Please
> test thoroughly before using in production as it can affect your queries.
# 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_URL,
},
}),
})
```
`@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` and 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. |
| `readReplicasAfterWriteInterval` | How long (ms) after a write to keep routing reads to the primary instead of a replica. Prevents stale reads caused by replication lag. Only relevant when `readReplicas` is set. Default `2000`. Set to `0` to disable. |
| `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`](/docs/v3/database/migrations.md) 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](/docs/v3/database/migrations.md#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 |
| `busyTimeout` | Maximum time in milliseconds to wait when the database is locked. Default is `0`. |
| `wal` | Enable Write-Ahead Logging (WAL) mode for improved performance and concurrency. [More Details](#wal-mode) |
## 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`](/docs/v3/database/migrations.md) 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](/docs/v3/database/migrations.md#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
> **Warning**
>
> 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/3.x/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/).
> **Note**
>
> 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.
### WAL Mode
You can enable Write-Ahead Logging (WAL) mode for improved performance and concurrency by passing the `wal` option to the SQLite adapter.
```ts title="Example SQLite Adapter with WAL"
import { sqliteAdapter } from '@payloadcms/db-sqlite'
export default buildConfig({
// Your config goes here
collections: [
// Collections go here
],
// Configure the SQLite adapter here
db: sqliteAdapter({
wal: true, // Enable WAL mode with default settings
client: {
url: process.env.DATABASE_URL,
},
}),
})
```
Settings for the `wal` option:
| Setting | Description | Default |
| ------------------ | ------------------------------------------------------------------------------------------------- | ----------------- |
| `journalSizeLimit` | Max size of the WAL file before checkpointing to the main DB. | `67108864` (64MB) |
| `synchronous` | WAL sync mode: `EXTRA`, `FULL`, `NORMAL`, `OFF`. Controls balance of performance vs. data safety. | `'FULL'` |
# 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](/docs/v3/database/overview.md), as well as automatically generate the corresponding UI within the [Admin Panel](/docs/v3/admin/overview.md).
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](/docs/v3/fields/overview.md#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).
> **Note**
>
> Payload also generates fields based on your collection and global
> configuration. See [Fields Generated by Payload](/docs/v3/fields/default-fields.md) to learn
> which fields you can override and how their configurations are merged.
To configure fields, use the `fields` property in your [Collection](/docs/v3/configuration/collections.md) or [Global](/docs/v3/configuration/globals.md) 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](/docs/v3/admin/overview.md).
To configure fields, use the `fields` property in your [Collection](/docs/v3/configuration/collections.md) or [Global](/docs/v3/configuration/globals.md) 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](/docs/v3/database/overview.md). 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](/docs/v3/fields/array.md) - for repeating content, supports nested fields
- [Blocks](/docs/v3/fields/blocks.md) - for block-based content, supports nested fields
- [Checkbox](/docs/v3/fields/checkbox.md) - saves boolean true / false values
- [Code](/docs/v3/fields/code.md) - renders a code editor interface that saves a string
- [Date](/docs/v3/fields/date.md) - renders a date picker and saves a timestamp
- [Email](/docs/v3/fields/email.md) - ensures the value is a properly formatted email address
- [Group](/docs/v3/fields/group.md) - nests fields within a keyed object
- [JSON](/docs/v3/fields/json.md) - renders a JSON editor interface that saves a JSON object
- [Number](/docs/v3/fields/number.md) - saves numeric values
- [Point](/docs/v3/fields/point.md) - for location data, saves geometric coordinates
- [Radio](/docs/v3/fields/radio.md) - renders a radio button group that allows only one value to be selected
- [Relationship](/docs/v3/fields/relationship.md) - assign relationships to other collections
- [Rich Text](/docs/v3/fields/rich-text.md) - renders a fully extensible rich text editor
- [Select](/docs/v3/fields/select.md) - renders a dropdown / picklist style value selector
- [Tabs (Named)](/docs/v3/fields/tabs.md) - similar to group, but renders nested fields within a tabbed layout
- [Text](/docs/v3/fields/text.md) - simple text input that saves a string
- [Textarea](/docs/v3/fields/textarea.md) - similar to text, but allows for multi-line input
- [Upload](/docs/v3/fields/upload.md) - 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](/docs/v3/admin/overview.md), or to add custom UI components.
Here are the available Presentational Fields:
- [Collapsible](/docs/v3/fields/collapsible.md) - nests fields within a collapsible component
- [Row](/docs/v3/fields/row.md) - aligns fields horizontally
- [Tabs (Unnamed)](/docs/v3/fields/tabs.md) - nests fields within a tabbed layout. It is not presentational if the tab has a name.
- [Group (Unnamed)](/docs/v3/fields/group.md) - nests fields within a keyed object. It is not presentational if the group has a name.
- [UI](/docs/v3/fields/ui.md) - 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](/docs/v3/fields/join.md) - 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](/docs/v3/custom-components/overview.md), you can override the entirety of
> how a component functions within the [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/admin/overview.md). 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',
}
```
> **Warning**
>
> 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
}
```
Since field names map directly to keys in API response objects and to column names in the database, they should follow JavaScript identifier conventions: start with a letter or underscore, and contain only letters, numbers, and underscores. Just like all Payload field names are strings, all Payload response objects use strings as their keys — so treat field names as you would a JavaScript variable name.
> **Warning**
>
> Avoid field names that start with a digit (e.g. `'1'`, `'2field'`) or contain
> hyphens (e.g. `'field-name'`). While these are valid strings in TypeScript,
> they can cause unexpected behavior when Payload accesses data using
> dot-notation paths, generates GraphQL schemas, or maps fields to database
> columns. GraphQL identifiers do not allow hyphens, so hyphenated field names
> will cause schema generation to fail.
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](/docs/v3/hooks/overview.md) 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](/docs/v3/hooks/fields.md) documentation.
### Field-level Access Control
In addition to being able to define [Access Control](/docs/v3/access-control/overview.md) 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](/docs/v3/access-control/fields.md) documentation.
### Disabling Field Duplication
Fields are copied from the source document when a document is duplicated. To prevent a field's
source value from being copied, set `disableDuplicate` to `true`:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
name: 'secret',
type: 'text',
disableDuplicate: true, // highlight-line
defaultValue: 'new-secret',
}
```
This option is available on fields that store their own value, including named groups and tabs. For
layout-only rows, collapsibles, unnamed groups, and unnamed tabs, set it on the nested fields that
own the values you do not want copied.
When the field is omitted from the duplicate request, Payload applies its normal create defaults.
For groups, nested field defaults are applied. Arrays and blocks use their field-level default value,
if configured, and then apply nested defaults to the default rows or blocks. Setting
`disableDuplicate` on a nested field preserves its parent row or block and applies that field's
default.
Request data may still provide a value for the field, subject to its normal `create` access control.
The option is not bypassed by `overrideAccess`, and `beforeDuplicate` hooks do not run for a disabled
field or its descendants.
### Default Values
Fields can be optionally prefilled with initial values. This is used in both the [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/database/overview.md) 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](/docs/v3/configuration/i18n.md#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/3.x/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,
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
},
],
}
```
> **Success**
>
> For more performance tips, see the [Performance
> documentation](/docs/v3/performance/overview.md).
## Admin Options
You can customize the appearance and behavior of fields within the [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/custom-components/overview.md) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`position`** | Use `'sidebar'` to render the field in the sidebar or `'main'` (default) to keep it in the main area. |
| **`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](/docs/v3/admin/overview.md) 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](/docs/v3/custom-components/overview.md), 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](/docs/v3/configuration/localization.md).
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](/docs/v3/configuration/i18n.md) |
> **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](/docs/v3/admin/overview.md), 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](/docs/v3/custom-components/overview.md#building-custom-components)._
> **Warning**
>
> 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-beforeinput), and
> [`afterInput`](#afterinput-and-beforeinput) properties.
##### Default Props
All Field Components receive the following props by default:
| Property | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](/docs/v3/admin/preferences.md) 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](/docs/v3/configuration/localization.md). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](/docs/v3/authentication/overview.md). |
| **`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](/docs/v3/configuration/i18n.md) object. |
| **`payload`** | The [Payload](/docs/v3/local-api/overview.md) 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](/docs/v3/authentication/overview.md). |
| **`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`](/docs/v3/admin/react-hooks.md#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} />
}
```
> **Success**
>
> For a complete list of all available React hooks, see the [Payload React
> Hooks](/docs/v3/admin/react-hooks.md) documentation. For additional help, see [Building
> Custom Components](/docs/v3/custom-components/overview.md#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
},
},
}
```
#### Example: Custom Cell Component
```ts
'use client'
import type { DefaultCellComponentProps } from 'payload'
export const MyCustomCellComponent: React.FC = ({
cellData,
field,
rowData,
}) => {
const price = cellData as number
const currency = rowData.currency || 'USD'
return (
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(price)}
)
}
```
> **Important:** Use `DefaultCellComponentProps` for client components (with
> `'use client'` directive) and `DefaultServerCellComponentProps` for server
> components. Omitting `'use client'` will change the expected prop types.
All Cell Components receive the following props:
| Property | Description |
| --------------------- | ---------------------------------------------------------------------------- |
| **`cellData`** | The data value for this specific cell. |
| **`collectionSlug`** | The slug of the collection being rendered. |
| **`field`** | The field configuration object. |
| **`rowData`** | An object containing all data for the current row. |
| **`className`** | Optional CSS class name for styling the cell. |
| **`columnIndex`** | Optional index number of the column. |
| **`customCellProps`** | Optional additional custom props passed to the cell. |
| **`link`** | Optional boolean representing whether this cell should be wrapped in a link. |
| **`linkURL`** | Optional URL string for the link. |
| **`onClick`** | Optional function called when the cell is clicked. |
| **`viewType`** | Optional string representing the type of view being rendered. |
> **Note:** Cell components receive a different set of props than Field
> components, optimized specifically for rendering data in the List View. They
> do not receive the standard Field component props like `docPreferences`,
> `validate`, or `path`.
For details on how to build Custom Components themselves, see [Building Custom Components](/docs/v3/custom-components/overview.md#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.
All Custom Filter Components receive the following props:
| Property | Description |
| -------------- | -------------------------------------------------- |
| **`disabled`** | Boolean indicating whether the filter is disabled. |
| **`onChange`** | Function to call when the filter value changes. |
| **`operator`** | The comparison operator being used for the filter. |
| **`value`** | The current value of the filter. |
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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md) 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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md#building-custom-components).
## TypeScript
You can import the Payload `Field` type as well as other common types from the `payload` package. [More details](/docs/v3/typescript/overview.md).
```ts
import type { Field } from 'payload'
```
# Fields Generated by Payload
Source: https://payloadcms.com/docs/fields/default-fields
Payload automatically adds fields to collections and globals based on their configuration. For example, collections receive an `id` field, auth-enabled collections receive fields such as `email`, and upload-enabled collections receive fields such as `filename`.
You can override the configuration of supported generated fields by defining a top-level field with the same `name` in your collection or global `fields` array. Payload merges auth, upload, and version field overrides into its default field configuration, preserving properties that you do not override, including hooks. Custom ID and timestamp fields replace the generated field instead.
> **Warning**
>
> Only the fields listed on this page support overrides. Other internal fields
> are reserved for Payload and should not be added to your fields array.
## Example
The following auth collection further restricts writes to Payload's default `apiKey` field. Field
overrides replace matching access functions, so the custom functions build on Payload's
`canAccessAdmin` boundary before applying an application role:
```ts
import {
canAccessAdmin,
type CollectionConfig,
type FieldAccess,
UnauthorizedError,
} from 'payload'
const canCreateOrUpdateAPIKeys: FieldAccess = async ({ req }) => {
try {
await canAccessAdmin({ req })
} catch (error) {
if (error instanceof UnauthorizedError) {
return false
}
throw error
}
return req.user?.roles?.includes('api-key-admin') === true
}
export const Users: CollectionConfig = {
slug: 'users',
auth: {
useAPIKey: true,
},
fields: [
{
name: 'apiKey', // highlight-line
type: 'text',
access: {
create: canCreateOrUpdateAPIKeys,
update: canCreateOrUpdateAPIKeys,
},
},
],
}
```
## Document fields
The following fields are available on collections and globals without enabling authentication, uploads, or versions:
| Field | Used by | Override behavior |
| ----------- | ------------------------------------------- | --------------------------------------------------------------------------- |
| `id` | Collections | Replaces the generated ID field. See [Custom ID fields](#custom-id-fields). |
| `createdAt` | Collections with timestamps and all globals | Replaces the generated field. Keep this as a `date` field. |
| `updatedAt` | Collections with timestamps and all globals | Replaces the generated field. Keep this as a `date` field. |
| `deletedAt` | Collections with `trash` enabled | Replaces the generated field. Keep this as a `date` field. |
### Custom ID fields
All [Collections](/docs/v3/configuration/collections.md) 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',
},
],
}
```
> **Warning**
>
> Custom ID fields can only be [`Number`](/docs/v3/fields/number.md) or [`Text`](/docs/v3/fields/text.md) fields.
> Custom text IDs must not contain `/` or `.` characters.
## Authentication fields
Authentication fields are added to a collection according to its [auth configuration](/docs/v3/authentication/overview.md). Payload deep-merges matching fields from your collection's `fields` array into these defaults, except for the special `password` override described below.
| Fields | Added when |
| -------------------------------------------------------------------- | --------------------------------------------- |
| `email`, `password`, `resetPasswordToken`, `resetPasswordExpiration` | The local authentication strategy is enabled. |
| `username` | `auth.loginWithUsername` is enabled. |
| `enableAPIKey` (legacy), `apiKey`, `apiKeyIndex`, `hasAPIKey` | `auth.useAPIKey` is enabled. |
| `_verified`, `_verificationToken` | `auth.verify` is enabled. |
| `loginAttempts`, `lockUntil` | `auth.maxLoginAttempts` is greater than `0`. |
| `sessions` | `auth.useSessions` is enabled. |
The `password` override is also applied to the generated new-password and confirm-password fields in the Admin Panel. You may need to set `hidden: true` on the override to prevent an additional field from being displayed.
The `salt` and `hash` fields are reserved and cannot be overridden.
## Upload fields
The following fields are added to [upload-enabled collections](/docs/v3/upload/overview.md). Payload deep-merges matching fields from your collection's `fields` array into these defaults.
| Fields | Added when |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `filename`, `mimeType`, `filesize`, `width`, `height`, `url`, `thumbnailURL` | Uploads are enabled. |
| `focalX`, `focalY` | Focal points are enabled, or `imageSizes` or `resizeOptions` is configured. |
| `sizes` | `imageSizes` is configured. Matching fields nested under `sizes` are also merged recursively. |
The `file` field is reserved and cannot be overridden.
## Version fields
The `_status` field is added to collections and globals when [drafts](/docs/v3/versions/drafts.md) are enabled. Define a top-level `_status` field to merge custom configuration into Payload's default field.
# Array Field
Source: https://payloadcms.com/docs/fields/array
The Array Field is used when you need to have a set of "repeating" [Fields](/docs/v3/fields/overview.md). 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](/docs/v3/fields/upload.md)) and a caption ([text field](/docs/v3/fields/text.md))
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/v3/fields/relationship.md)), an "open in new tab" [checkbox field](/docs/v3/fields/checkbox.md)
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/v3/fields/date.md)), label ([text field](/docs/v3/fields/text.md)), and Learn More page [relationship](/docs/v3/fields/relationship.md)

_Admin Panel screenshot of an Array field with two Rows_
To create an Array Field, set the `type` to `array` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#field-names). |
| **`label`** | Text used as the heading in the [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/admin/overview.md) and the backend. [More details](/docs/v3/fields/overview.md#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/v3/database/postgres.md)). 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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._
> **Warning**
>
> Setting `unique: true` on a field **inside** an Array creates a
> collection-wide database unique index — not a per-document one. No two
> documents in the collection can share the same value at that nested path, and
> on MongoDB, documents without the array will collide on `null`. To enforce
> uniqueness within a single document's rows, use a custom
> [`validate`](/docs/v3/fields/overview.md#validation) function on this Array field.
> [More details](/docs/v3/database/indexes.md#unique-fields).
## Admin Options
To customize the appearance and behavior of the Array Field in the [Admin Panel](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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`.

_Admin Panel screenshot of add Blocks drawer view_
To add a Blocks Field, set the `type` to `blocks` in your [Field Config](/docs/v3/fields/overview.md):
```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
### Block Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/v3/fields/overview.md#field-names). |
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** \* | Array of [block configs](/docs/v3/fields/blocks.md#config-options) 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](/docs/v3/fields/overview.md#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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._
> **Warning**
>
> Setting `unique: true` on a field **inside** a Block creates a collection-wide
> database unique index — not a per-document one. No two documents in the
> collection can share the same value at that nested path, and on MongoDB,
> documents without the block will collide on `null`. To enforce uniqueness
> within a single document's blocks, use a custom
> [`validate`](/docs/v3/fields/overview.md#validation) function on this Blocks field.
> [More details](/docs/v3/database/indexes.md#unique-fields).
### Block Admin Options
To customize the appearance and behavior of the Blocks Field in the [Admin Panel](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/rich-text/overview.md), 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'
```
## Copying and Pasting Blocks
The Admin Panel includes built-in copy and paste support for both individual block rows and entire Blocks fields. This is available through the row action menu (the `...` button on each block) and from the field-level action menu in the field header.
### Row-level vs. Field-level
- **Row copy/paste** — copies a single block and lets you paste it into any position within any compatible Blocks field
- **Field copy/paste** — copies all blocks in the field at once and pastes them as the full contents of a target field, replacing whatever was there
### How it differs from Duplicate
The **Duplicate** action creates an immediate in-place copy of a block, inserted directly below the original — always within the same field on the same document.
**Copy/paste** is designed for moving data across fields or documents:
- Copy a block on `Page A`, open `Page B`, and paste it into that page's blocks field
- Copy a block from one Blocks field and paste it into a different Blocks field on the same document
### How it works
Copied data is written to `localStorage` under the key `_payloadClipboard`. This means:
- The clipboard persists across browser tabs (same origin)
- It is not shared between different Payload origins
When pasting, Payload validates that the copied block type exists in the target field's `blocks` config. If the schemas are incompatible, the paste action will not be available. All block and nested array IDs are automatically regenerated on paste to prevent duplicate key errors.
> **Warning**
>
> **"Paste Fields" replaces all blocks, even when a single row is on the clipboard.**
>
> If you have copied a single block row and then use **Paste Fields** (the field-level paste action in the field header), all existing blocks in the target field will be replaced with just that one copied row.
>
> To add a copied row to a field that already has content, use **Paste Row** instead: add a new empty block first, then use the row-level **Paste Row** action on that new row to replace it with the copied data.
## 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. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#custom-field-schemas). |
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
| **`dbName`** | Custom table name for this block type when using SQL Database Adapter ([Postgres](/docs/v3/database/postgres.md)). Auto-generated from slug if not defined. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._
### 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) |
| **`images`** | Custom images for the block in different UI contexts. Supports `icon` (20x20px for Lexical menus/toolbars) and `thumbnail` (3:2 ratio for block selection drawer). Each can be a URL string or `{ url, alt }` object. [More details](#block-image-guidelines). | |
### Block Image Guidelines
The `images` property lets you provide custom images for each display context:
- **`images.icon`** - Small icon for Lexical editor menus and toolbars (displayed at 20x20px)
- **`images.thumbnail`** - Larger thumbnail for block selection drawer (3:2 aspect ratio)
Each can be either a plain URL string or an object `{ url: string, alt?: string }`.
#### Icon (`images.icon`)
Used in Lexical editor slash menus, toolbar dropdowns, and inline block menus.
**Requirements**:
- Displayed at **20x20 pixels**
- **Square images** (1:1 aspect ratio) work best
- Falls back to `images.thumbnail`, then to default block icon
**Recommendations**:
- Use SVG for crisp rendering at small sizes
- Keep designs simple with bold shapes
- Provide web-optimized images (SVG, PNG with transparency)
#### Thumbnail (`images.thumbnail`)
Used in the block selection drawer when editors add blocks to a blocks field.
**Requirements**:
- **3:2 aspect ratio** (e.g., 480x320, 600x400, 900x600)
- Images are scaled using `object-fit: cover`
- Non-matching aspect ratios will be **cropped** to fill the container
**Recommendations**:
- Use images with **3:2 aspect ratio** to avoid unwanted cropping
- Keep important visual content **centered** in your image
- Provide web-optimized images (JPEG, PNG, WebP) for faster loading
#### Usage Examples
**Example 1: Using both icon and thumbnail with alt text**
```ts
const QuoteBlock: Block = {
slug: 'quote',
admin: {
images: {
icon: {
url: 'https://example.com/icons/quote-icon-20x20.svg',
alt: 'Quote icon',
},
thumbnail: {
url: 'https://example.com/thumbnails/quote-block-480x320.jpg',
alt: 'Quote block',
},
},
},
fields: [
{
name: 'quoteText',
type: 'text',
required: true,
},
],
}
```
**Example 2: Using URL strings (no alt text)**
```ts
const CallToActionBlock: Block = {
slug: 'cta',
admin: {
images: {
icon: 'https://example.com/icons/cta-20x20.svg',
thumbnail: 'https://example.com/thumbnails/cta-block-480x320.jpg',
},
},
fields: [
{
name: 'buttonText',
type: 'text',
},
],
}
```
**Example 3: Using only icon**
```ts
const DividerBlock: Block = {
slug: 'divider',
admin: {
images: {
icon: 'https://example.com/icons/divider-20x20.svg',
},
},
fields: [],
}
```
If no `images` is provided, default graphics are displayed automatically in both contexts.
### 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.
### filterOptions 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.

_Admin Panel screenshot of Checkbox field with Text field below_
To add a Checkbox Field, set the `type` to `checkbox` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md) with a code editor styled interface.

_This field is using the `monaco-react` editor syntax highlighting._
To add a Code Field, set the `type` to `code` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/admin/overview.md) with a code editor styled interface. This is different from the [Code Field](/docs/v3/fields/code.md) which saves the value as a string in the database.

_This field is using the `monaco-react` editor syntax highlighting._
To add a JSON Field, set the `type` to `json` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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/docs.html#variables/editor_editor_api.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.

_Admin Panel screenshot of a Collapsible field_
To add a Collapsible Field, set the `type` to `collapsible` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/admin/overview.md) with a customizable time picker interface.

_This field is using the `react-datepicker` component for UI._
To add a Date Field, set the `type` to `date` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/admin/overview.md#timezones) or on the field config itself which accepts the following config as well:
| Property | Description |
| -------------------- | ---------------------------------------------------------------------------------------- |
| `defaultTimezone` | A value for the default timezone to be set. |
| `supportedTimezones` | An array of supported timezones with label and value object. |
| `required` | If true, the timezone selection will be required even if the date is not. |
| `override` | A function to customize the generated timezone field. [More details](#timezone-override) |
```ts
{
name: 'date',
type: 'date',
timezone: {
defaultTimezone: 'America/New_York',
supportedTimezones: [
{ label: 'New York', value: 'America/New_York' },
{ label: 'Los Angeles', value: 'America/Los_Angeles' },
{ label: 'London', value: 'Europe/London' },
],
},
}
```
> **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.
### Timezone Override
The `override` function allows you to customize the auto-generated timezone select field at a granular level. This is useful when you need to modify admin options like visibility, descriptions, or other field properties.
```ts
{
name: 'publishedAt',
type: 'date',
label: 'Published At',
timezone: {
override: ({ baseField }) => ({
...baseField,
admin: {
...baseField.admin,
disableListColumn: true, // Hide from list view columns
},
}),
},
}
```
The `override` function receives an object with `baseField` (the default timezone select field) and must return a valid field configuration. The base field includes:
- `name`: The timezone field name (e.g., `publishedAt_tz`)
- `type`: Always `'select'`
- `options`: The available timezone options
- `defaultValue`: The default timezone value
- `required`: Whether the timezone is required
- `label`: Auto-generated from the parent field's label (e.g., "Published At Tz")
- `admin.hidden`: `true` by default
> **Note**
>
> We recommend changing the available options only via the supportedTimezones
> config so that the right validations are run against your timezones.
### Custom UTC Offsets
In addition to IANA timezone names (like `America/New_York`), you can also use fixed UTC offsets in the `±HH:mm` format:
```ts
{
name: 'eventTime',
type: 'date',
timezone: {
supportedTimezones: [
{ label: 'UTC+5:30 (India)', value: '+05:30' },
{ label: 'UTC-8 (Pacific)', value: '-08:00' },
{ label: 'UTC+0', value: '+00:00' },
],
},
}
```
You can also mix IANA timezones with custom UTC offsets:
```ts
{
name: 'scheduledAt',
type: 'date',
timezone: {
supportedTimezones: [
{ label: 'New York', value: 'America/New_York' },
{ label: 'UTC+5:30', value: '+05:30' },
{ label: 'UTC', value: 'UTC' },
],
},
}
```
> **Note**
>
> Custom UTC offsets are fixed and do not account for daylight saving time (DST)
> adjustments. If you need automatic DST handling, use IANA timezone names
> instead (e.g., `America/New_York` rather than `-05:00`).
#### GraphQL Enum Names
When using offset timezones with GraphQL, the offset values are transformed to valid GraphQL enum names using the `_TZOFFSET_` prefix:
| Offset Value | GraphQL Enum Name |
| ------------ | ----------------------- |
| `+05:30` | `_TZOFFSET_PLUS_05_30` |
| `-08:00` | `_TZOFFSET_MINUS_08_00` |
| `+00:00` | `_TZOFFSET_PLUS_00_00` |
Similarly, IANA timezone names are also transformed (e.g., `America/New_York` becomes `America_New_York`).
```graphql
# Query returns the enum name
query {
Event {
scheduledAt_tz # Returns "_TZOFFSET_PLUS_05_30"
}
}
# Mutations use the enum name
mutation {
createEvent(data: { scheduledAt_tz: _TZOFFSET_PLUS_05_30 }) {
scheduledAt_tz
}
}
```
The actual value (`+05:30`) is stored in the database and returned by the REST API.
# Email Field
Source: https://payloadcms.com/docs/fields/email
The Email Field enforces that the value provided is a valid email address.

_Admin Panel screenshot of an Email field_
To create an Email Field, set the `type` to `email` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md) to be nested under a common property name. It also groups fields together visually in the [Admin Panel](/docs/v3/admin/overview.md).

_Admin Panel screenshot of a Group field_
To add a Group Field, set the `type` to `group` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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 `name` will render just the grouped fields (no nested object is created). If you want the group to appear as a titled section in the Admin UI, set a `label`.
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
label: 'Page meta', // label only → presentational
type: 'group', // required
fields: [
{
name: 'title',
type: 'text',
required: true,
minLength: 20,
maxLength: 100,
},
{
name: 'description',
type: 'textarea',
required: true,
minLength: 40,
maxLength: 160,
},
],
},
],
}
```
## Named group
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageMeta', // name → nested object in data
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.

_Admin Panel screenshot of a Number field_
To add a Number Field, set the `type` to `number` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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.

_Admin Panel screenshot of a Point field_
To add a Point Field, set the `type` to `point` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) 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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md).

_Admin Panel screenshot of a Radio field_
To add a Radio Field, set the `type` to `radio` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/database/postgres.md)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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.

_Admin Panel screenshot of a Relationship field_
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](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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](/docs/v3/queries/depth.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#virtual-field-configuration) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/v3/graphql/overview.md#field-complexity) |
_\* An asterisk denotes that a property is required._
> **Tip:** The [Depth](/docs/v3/queries/depth.md) 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/queries/overview.md), 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](/docs/v3/queries/overview.md).
> **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](/docs/v3/fields/join.md).
## 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

_Admin Panel screenshot of Join field_
For the Join field to work, you must have an existing [relationship](/docs/v3/fields/relationship.md) or [upload](/docs/v3/fields/upload.md) 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](/docs/v3/fields/overview.md):
```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.
> **Success**
>
> 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.
> **Warning**
>
> 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.
> **Success**
>
> 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](/docs/v3/fields/overview.md#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](/docs/v3/configuration/collections.md#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](/docs/v3/queries/depth.md#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](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/graphql/overview.md#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](/docs/v3/fields/overview.md#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',
},
},
})
```
> **Warning**
>
> 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.
Admin Panel screenshot of a Rich Text field
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/rich-text/overview.md). |
| **`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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), you can use the `admin` option. The Rich Text Field inherits all the default options from the base [Field Admin Config](/docs/v3/fields/overview.md#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](/docs/v3/rich-text/overview.md).
# Row Field
Source: https://payloadcms.com/docs/fields/row
The Row Field is presentational-only and only affects the [Admin Panel](/docs/v3/admin/overview.md). By using it, you can arrange [Fields](/docs/v3/fields/overview.md) next to each other horizontally.

_Admin Panel screenshot of a Row field_
To add a Row Field, set the `type` to `row` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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.

_Admin Panel screenshot of a Select field_
To add a Select Field, set the `type` to `select` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/v3/fields/overview.md#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](/docs/v3/database/postgres.md)). Auto-generated from name if not defined. |
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/v3/database/postgres.md)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/relationship.md) or [Upload](/docs/v3/fields/upload.md) 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/admin/overview.md) (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.

_Tabs field type used to separate Hero fields from Page Layout_
To add a Tabs Field, set the `type` to `tabs` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#field-names). |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/v3/typescript/generating-types.md#custom-field-interfaces) & [GraphQL type](/docs/v3/graphql/graphql-schema.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| **`admin`** | Admin-specific configuration. [More details](/docs/v3/fields/overview.md#admin-options). Currently supports `condition` and `description`. |
| **`id`** | An optional identifier for the tab. If `admin.condition` is used on an unnamed tab, an `id` is required (and will be auto-generated if not provided). |
_\* An asterisk denotes that a property is required._
## Conditional Tabs
Just like fields, individual tabs can be conditionally shown or hidden based on the value of other fields. This is done by providing a `condition` function to the `admin` property of the tab config.
When a tab's condition returns `false`, the tab and all of its fields will be hidden from the Admin Panel. If the currently active tab becomes hidden due to a condition change, Payload will automatically switch to the next available visible tab.
### Example
```ts
{
type: 'tabs',
tabs: [
{
label: 'Always Visible',
fields: [
{
name: 'showExtraTab',
type: 'checkbox',
},
],
},
{
label: 'Conditional Tab',
admin: {
condition: (data) => !!data.showExtraTab,
},
fields: [
{
name: 'extraField',
type: 'text',
},
],
},
],
}
```
## 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](/docs/v3/admin/overview.md) with a simple text input.

_Admin Panel screenshot of a Text field and read-only Text field_
To add a Text Field, set the `type` to `text` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`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](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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
> **Warning**
>
> 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`. |
| `disableUnique` | Disables the unique index on the field. Useful when you want to add a compound unique index to the collection `indexes` config instead, for example with the multi-tenant plugin where the slug is only unique per tenant, not globally |
| `useAsSlug` | The name of the top-level 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](/docs/v3/fields/overview.md#admin-options). |
| `required` | Require the slug field. Defaults to `true`. |
| `slugify` | Override the default slugify function. [More details](#custom-slugify-function). |
### 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) => {
const slugTextField = defaultField.fields[1]
if (slugTextField?.type === 'text') {
slugTextField.label = 'Custom Slug Label'
}
return defaultField
},
}),
// highlight-line
],
}
```
### Custom Slugify Function
You can also override the default slugify function of the slug field. This is necessary if the slug requires special treatment, such as character encoding, additional language support, etc.
This functions receives the value of the `useAsSlug` field as `valueToSlugify` and must return a string.
For example, if you wanted to use the [`slugify`](https://www.npmjs.com/package/slugify) package, you could do something like this:
```ts
import type { CollectionConfig } from 'payload'
import { slugField } from 'payload'
import slugify from 'slugify'
export const MyCollection: CollectionConfig = {
// ...
fields: [
// ...
slugField({
slugify: ({ valueToSlugify }) =>
slugify(valueToSlugify, {
// ...additional `slugify` options here
}),
}),
],
}
```
The following args are provided to the custom `slugify` function:
| Argument | Type | Description |
| ---------------- | ---------------- | ------------------------------------------------ |
| `valueToSlugify` | `string` | The value of the field specified in `useAsSlug`. |
| `data` | `object` | The full document data being saved. |
| `req` | `PayloadRequest` | The Payload request object. |
# Textarea Field
Source: https://payloadcms.com/docs/fields/textarea
The Textarea Field is nearly identical to the [Text Field](/docs/v3/fields/text.md) but it features a slightly larger input that is better suited to edit longer text.

_Admin Panel screenshot of a Textarea field and read-only Textarea field_
To add a Textarea Field, set the `type` to `textarea` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`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](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md#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](/docs/v3/admin/overview.md), 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](/docs/v3/fields/overview.md):
```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](/docs/v3/fields/overview.md#field). |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More details](/docs/v3/fields/overview.md#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](/docs/v3/upload/overview.md), 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

_Admin Panel screenshot of an Upload field_
To create an Upload Field, set the `type` to `upload` in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/configuration/collections.md) configured to allow
> [Uploads](/docs/v3/upload/overview.md).
## Config Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/v3/fields/overview.md#field-names). |
| **`relationTo`** \* | Provide a single collection `slug` or an array of slugs to allow this field to accept a relation to. **Note: the related collections 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](/docs/v3/queries/depth.md) |
| **`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. This creates a database-level unique index on the field's path. [More details](/docs/v3/database/indexes.md#unique-fields). |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/v3/fields/overview.md#validation). |
| **`index`** | Build an [index](/docs/v3/database/indexes.md) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/v3/authentication/overview.md), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](/docs/v3/hooks/fields.md). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](/docs/v3/access-control/fields.md). |
| **`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](/docs/v3/fields/overview.md#default-values). |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More details](/docs/v3/upload/overview.md#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/v3/configuration/localization.md) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [Admin Options](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/graphql/overview.md#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](/docs/v3/queries/overview.md), 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](/docs/v3/queries/overview.md).
> **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](/docs/v3/fields/join.md)
## Polymorphic Uploads
Upload fields can reference multiple upload collections by providing an array of collection slugs to the `relationTo` property.
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'media',
type: 'upload',
relationTo: ['images', 'documents', 'videos'], // references multiple upload collections
},
],
}
```
This can be combined with the `hasMany` property to allow multiple uploads from multiple collections.
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'media',
type: 'upload',
relationTo: ['images', 'documents', 'videos'], // references multiple upload collections
hasMany: true, // allows multiple uploads
},
],
}
```
# Access Control
Source: https://payloadcms.com/docs/access-control/overview
[Video: Overview of Payload Access Control](https://www.youtube.com/watch?v=DoPLyXG26Dg)
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](/docs/v3/admin/overview.md). 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](/docs/v3/access-control/collections.md)
- [Global Access Control](/docs/v3/access-control/globals.md)
- [Field Access Control](/docs/v3/access-control/fields.md)
## Default Access Control
Payload provides default Access Control so that your data is secured behind [Authentication](/docs/v3/authentication/overview.md) 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](/docs/v3/local-api/overview.md), 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](/docs/v3/authentication/operations.md#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](/docs/v3/authentication/operations.md#access), the `id`, `data`, `siblingData`, `blockData` and `doc` arguments
> will be `undefined`. Additionally, `Where` queries returned from access control functions will not be run - we'll assume the user does not have access instead.
>
> This is because Payload is executing your functions without referencing a specific Document.
If you use `id`, `data`, `siblingData`, `blockData` and `doc` 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](/docs/v3/access-control/overview.md) used to restrict access to Documents within a [Collection](/docs/v3/configuration/collections.md), as well as what they can and cannot see within the [Admin Panel](/docs/v3/admin/overview.md) as it relates to that Collection.
To add Access Control to a Collection, use the `access` property in your [Collection Config](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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`](/docs/v3/authentication/overview.md), the following additional options are available:
| Function | Allows/Denies Access |
| ------------ | ---------------------------------------------------------------------------------------- |
| **`admin`** | Used to restrict access to the [Admin Panel](/docs/v3/admin/overview.md). [More details](#admin). |
| **`unlock`** | Used to restrict which users can access the `unlock` operation. [More details](#unlock). |
If a Collection supports [Versions](/docs/v3/versions/overview.md), 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](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/queries/overview.md) to limit the Documents to only
> those that match the constraint. This can be helpful to restrict users' access
> to specific Documents. [More details](/docs/v3/queries/overview.md).
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'
import type { Page } from '@/payload-types'
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](/docs/v3/configuration/collections.md):
```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](/docs/v3/queries/overview.md) to limit the Documents to only
> those that match the constraint. This can be helpful to restrict users' access
> to specific Documents. [More details](/docs/v3/queries/overview.md).
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'
import type { User } from '@/payload-types'
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. |
> **Tip:** For Collections with [Drafts](/docs/v3/versions/drafts.md) enabled, you can
> use `update` access to [control who can
> publish](/docs/v3/versions/drafts.md#controlling-who-can-publish) by returning a query
> constraint on the `_status` field.
### Delete
Similarly to the Update function, returns a boolean or a [query constraint](/docs/v3/queries/overview.md) 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](/docs/v3/configuration/collections.md):
```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'
import type { Customer } from '@/payload-types'
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. |
| **`data`** | The data being set on the document. For [Trash-enabled](/docs/v3/trash/overview.md) collections, check `data.deletedAt` to differentiate between soft delete and permanent delete operations. |
> **Tip:** For Collections with [Trash](/docs/v3/trash/overview.md) enabled, you can use
> the `data` argument to allow users to soft delete (trash) documents while
> restricting permanent deletion. See [Trash Access
> Control](/docs/v3/trash/overview.md#access-control) for examples.
### Admin
If the Collection is used to access the [Admin Panel](/docs/v3/admin/overview.md#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](/docs/v3/configuration/collections.md):
```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](/docs/v3/authentication/operations.md#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](/docs/v3/authentication/overview.md#config-options).
To add Unlock Access Control to a Collection, use the `unlock` property in the [Collection Config](/docs/v3/configuration/collections.md):
```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](/docs/v3/versions/overview.md) 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](/docs/v3/configuration/collections.md):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionWithVersionsAccess: CollectionConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
> **Note:** Returning a [Query](/docs/v3/queries/overview.md) will apply the constraint
> to the [`versions` collection](/docs/v3/versions/overview.md#database-impact), not the
> original Collection.
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](/docs/v3/access-control/overview.md) used to restrict access to [Global](/docs/v3/configuration/globals.md) Documents, as well as what they can and cannot see within the [Admin Panel](/docs/v3/admin/overview.md) as it relates to that Global.
To add Access Control to a Global, use the `access` property in your [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/configuration/globals.md), use the `access` property in the [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/versions/overview.md), 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](/docs/v3/queries/overview.md) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](/docs/v3/configuration/globals.md), use the `access` property in the [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/queries/overview.md) which limits who can update this global based on its current properties.
To add update Access Control to a [Global](/docs/v3/configuration/globals.md), use the `access` property in the [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/versions/overview.md) 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 Global, use the `readVersions` property in the [Global Config](/docs/v3/configuration/globals.md):
```ts
import type { GlobalConfig } from 'payload'
export const GlobalWithVersionsAccess: GlobalConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
}
```
> **Note:** Returning a [Query](/docs/v3/queries/overview.md) will apply the constraint
> to the [`versions` collection](/docs/v3/versions/overview.md#database-impact), not the
> original Global.
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](/docs/v3/access-control/overview.md) used to restrict access to specific [Fields](/docs/v3/fields/overview.md) within a Document.
To add Access Control to a Field, use the `access` property in your [Field Config](/docs/v3/fields/overview.md):
```ts
import type { Field } from 'payload'
export const FieldWithAccessControl: Field = {
// ...
access: {
// highlight-line
// ...
},
}
```
> **Note:** Field Access Control does not support returning
> [Query](/docs/v3/queries/overview.md) constraints like [Collection Access
> Control](/docs/v3/access-control/collections.md) 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](/docs/v3/fields/overview.md):
```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.
[Video: Payload Hooks Explained: When to Use Them, and when to pass logic to the Jobs Queue](https://www.youtube.com/watch?v=zYhPeBi5PaU)
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](/docs/v3/hooks/collections.md)
- [Global Hooks](/docs/v3/hooks/globals.md)
- [Field Hooks](/docs/v3/hooks/fields.md)
> **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](/docs/v3/admin/react-hooks.md).
## 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/configuration/collections.md) 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. |
## Awaited vs. non-blocking hooks
Hooks can either block the request until they finish or run without blocking it. What matters is whether your hook returns a Promise.
Awaited (blocking): If your hook returns a Promise (for example, if it’s declared async), Payload will wait for it to resolve before continuing that lifecycle step. Use this when your hook needs to modify data or influence the response. Hooks that return Promises run in series at the same lifecycle stage.
Non-blocking (sometimes called “fire-and-forget”): If your hook does not return a Promise (returns nothing), Payload will not wait for it to finish. This can be useful for side-effects that don’t affect the outcome of the operation, but keep in mind that any work started this way might continue after the request has already completed.
**Declaring a function with async does not make it “synchronous.” The async keyword simply makes the function return a Promise automatically — which is why Payload then awaits it.**
> **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-to-the-jobs-queue). That will free up the request to
> continue processing without waiting for the task to complete.
**Awaited**
```ts
const beforeChange = async ({ data }) => {
const enriched = await fetchProfile(data.userId) // Payload waits here
return { ...data, profile: enriched }
}
```
**Non-blocking**
```ts
const afterChange = ({ doc }) => {
// Trigger side-effect without blocking
void pingAnalyticsService(doc.id)
// No return → Payload does not wait
}
```
## 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.
## Custom Error Messages
You might want to return custom error messages in the API or admin panel in your hooks, if an operation cannot be performed - e.g. if a rate limit was exceeded. For this you can leverage the `APIError` class like this:
```ts
import { APIError, type CollectionBeforeChangeHook } from 'payload'
const beforeChangeHook: CollectionBeforeChangeHook = async ({ data }) => {
...
if (rateLimitExceeded) {
throw new APIError('You have sent too many requests', 429)
}
return data
}
```
## 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.
> **Success**
>
> For more performance tips, see the [Performance
> documentation](/docs/v3/performance/overview.md).
### 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](/docs/v3/hooks/context.md) to avoid infinite loops or to prevent 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](/docs/v3/hooks/context.md).
### Offloading to the jobs queue
If your hooks perform any long-running tasks that don't directly affect the request lifecycle, consider offloading them to the [jobs queue](/docs/v3/jobs-queue/overview.md). 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](/docs/v3/jobs-queue/overview.md).
# Collection Hooks
Source: https://payloadcms.com/docs/hooks/collections
Collection Hooks are [Hooks](/docs/v3/hooks/overview.md) that run on Documents within a specific [Collection](/docs/v3/configuration/collections.md). 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](/docs/v3/configuration/collections.md):
```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](/docs/v3/hooks/fields.md).
## Config Options
All Collection Hooks accept an array of [synchronous or asynchronous functions](/docs/v3/hooks/overview.md#awaited-vs-non-blocking-hooks). 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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'
import type { Post } from '@/payload-types'
const beforeValidateHook: CollectionBeforeValidateHook = async ({
data, // Typed as Partial
}) => {
return data
}
```
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](/docs/v3/hooks/context.md). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The full document before changes are applied. Present on updates; undefined on creates. Use this to read the document id and any unchanged fields. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
**Why isn’t id in data?**
`data` only represents the delta you’re saving. Read `originalDoc.id` during updates, and prefer `afterChange` if you need the `id` during creates
> **Warning**
>
> On update operations, data contains only the fields being changed. It may omit
> id and any unchanged fields. Use originalDoc to read existing values. On
> create, data is the new submission and the document id is not yet available at
> this stage.
### 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'
export const requireTitleOnUpdate: CollectionBeforeChangeHook = async ({
data, // Partial — changed fields only
originalDoc, // Full doc before changes (defined on update)
operation, // 'create' | 'update'
}) => {
// Need the id? Don't expect it in `data`.
const id = operation === 'update' ? originalDoc.id : undefined
// Example: enforce that title cannot be cleared on update
if (operation === 'update' && 'title' in (data ?? {}) && !data.title) {
throw new Error(`Document ${id} must have a title`)
}
return data
}
```
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The full document before changes are applied. Present on updates; undefined on creates. Use this to read the document id and any unchanged fields. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
**Why isn’t id in data?**
`data` only represents the delta you’re saving. Read `originalDoc.id` during updates, and prefer `afterChange` if you need the `id` during creates
> **Warning**
>
> On update operations, data contains only the fields being changed. It may omit
> id and any unchanged fields. Use originalDoc to read existing values. On
> create, data is the new submission and the document id is not yet available at
> this stage.
### 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'
import type { Post } from '@/payload-types'
const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // Typed as Post
previousDoc, // Typed as Post
}) => {
return doc
}
```
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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'
import type { Post } from '@/payload-types'
const beforeReadHook: CollectionBeforeReadHook = async ({
doc, // Typed as Post
}) => {
return doc
}
```
The following arguments are provided to the `beforeRead` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`doc`** | The resulting Document after changes are applied. |
| **`query`** | The [Query](/docs/v3/queries/overview.md) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) 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'
import type { Post } from '@/payload-types'
const afterReadHook: CollectionAfterReadHook = async ({
doc, // Typed as Post
}) => {
return doc
}
```
The following arguments are provided to the `afterRead` hook:
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`doc`** | The resulting Document after changes are applied. |
| **`query`** | The [Query](/docs/v3/queries/overview.md) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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.
```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](/docs/v3/configuration/collections.md) 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](/docs/v3/local-api/overview.md) 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](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/configuration/collections.md) 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](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
| **`user`** | The user being logged in. |
### afterLogin
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
| **`token`** | The token generated for the user. |
| **`user`** | The user being logged in. |
### afterLogout
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
### afterMe
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
| **`response`** | The response to return. |
### afterRefresh
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) operations. |
| **`token`** | The newly refreshed user token. |
### afterForgotPassword
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/configuration/collections.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
### refresh
For [Auth-enabled Collections](/docs/v3/authentication/overview.md), 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](/docs/v3/authentication/overview.md), 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'
```
You can also pass a generic type to each hook for strongly-typed `doc`, `previousDoc`, and `data` properties:
```ts
import type { CollectionAfterChangeHook } from 'payload'
import type { Post } from '@/payload-types'
const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // Typed as Post
previousDoc, // Typed as Post
}) => {
return doc
}
```
# Global Hooks
Source: https://payloadcms.com/docs/hooks/globals
Global Hooks are [Hooks](/docs/v3/hooks/overview.md) that run on [Global](/docs/v3/configuration/globals.md) 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](/docs/v3/configuration/globals.md):
```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](/docs/v3/hooks/fields.md).
## Config Options
All Global Hooks accept an array of [synchronous or asynchronous functions](/docs/v3/hooks/overview.md#awaited-vs-non-blocking-hooks). 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](/docs/v3/configuration/globals.md) in which this Hook is running against. Available operation include: `countVersions`, `read`, `restoreVersion`, and `update`. |
| **`context`** | Custom context passed between Hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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'
import type { SiteSettings } from '@/payload-types'
const beforeValidateHook: GlobalBeforeValidateHook = async ({
data, // Typed as Partial
req,
originalDoc, // Typed as SiteSettings
}) => {
return data
}
```
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](/docs/v3/configuration/globals.md) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](/docs/v3/hooks/context.md). |
| **`data`** | The incoming data passed through the operation. |
| **`originalDoc`** | The full document before changes are applied. Present on updates; undefined on creates. Use this to read the document id and any unchanged fields. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
> **Warning**
>
> On update operations, data contains only the fields being changed. It may omit
> id and any unchanged fields. Use originalDoc to read existing values. On
> create, data is the new submission and the document id is not yet available at
> this stage.
### 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'
import type { SiteSettings } from '@/payload-types'
const beforeChangeHook: GlobalBeforeChangeHook = async ({
data, // Typed as Partial
req,
originalDoc, // Typed as SiteSettings
}) => {
return data
}
```
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](/docs/v3/configuration/globals.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`data`** | The incoming data passed through the operation. |
| **`originalDoc`** | The full document before changes are applied. Present on updates; undefined on creates. Use this to read the document id and any unchanged fields. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) operations. |
> **Warning**
>
> On update operations, data contains only the fields being changed. It may omit
> id and any unchanged fields. Use originalDoc to read existing values. On
> create, data is the new submission and the document id is not yet available at
> this stage.
### 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'
import type { SiteSettings } from '@/payload-types'
const afterChangeHook: GlobalAfterChangeHook = async ({
doc, // Typed as SiteSettings
previousDoc, // Typed as SiteSettings
req,
}) => {
return doc
}
```
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](/docs/v3/configuration/globals.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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](/docs/v3/configuration/globals.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/local-api/overview.md) 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'
import type { SiteSettings } from '@/payload-types'
const afterReadHook: GlobalAfterReadHook = async ({
doc, // Typed as SiteSettings
req,
findMany,
}) => {
return doc
}
```
The following arguments are provided to the `afterRead` hook:
| Option | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](/docs/v3/configuration/globals.md) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/queries/overview.md) of the request. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](/docs/v3/local-api/overview.md) 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'
```
You can also pass a generic type to each hook for strongly-typed `doc`, `previousDoc`, and `data` properties:
```ts
import type { GlobalAfterChangeHook } from 'payload'
import type { SiteSettings } from '@/payload-types'
const afterChangeHook: GlobalAfterChangeHook = async ({
doc, // Typed as SiteSettings
previousDoc, // Typed as SiteSettings
}) => {
return doc
}
```
# Field Hooks
Source: https://payloadcms.com/docs/hooks/fields
Field Hooks are [Hooks](/docs/v3/hooks/overview.md) 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](/docs/v3/hooks/collections.md) and [Global Hooks](/docs/v3/hooks/globals.md).
To add Hooks to a Field, use the `hooks` property in your [Field Config](/docs/v3/fields/overview.md):
```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 on 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](/docs/v3/graphql/overview.md). If you need to change the shape or type of data,
> consider [Collection Hooks](/docs/v3/hooks/collections.md) or [Global Hooks](/docs/v3/hooks/globals.md)
> instead.
To add hooks to a Field, use the `hooks` property in your [Field Config](/docs/v3/fields/overview.md):
```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](/docs/v3/configuration/collections.md) 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](/docs/v3/hooks/context.md). |
| **`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](/docs/v3/fields/overview.md) 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](/docs/v3/configuration/globals.md) 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](/docs/v3/access-control/overview.md). |
| **`path`** | The path to the [Field](/docs/v3/fields/overview.md) 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](/docs/v3/local-api/overview.md) operations. |
| **`schemaPath`** | The path of the [Field](/docs/v3/fields/overview.md) 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](/docs/v3/configuration/localization.md). |
| **`siblingFields`** | The sibling fields of the field which the hook is running against. |
| **`value`** | The value of the [Field](/docs/v3/fields/overview.md). |
> **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 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 { 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
}
```
### Practical Example with Generated Types
Here's a real-world example using generated Payload types:
```ts
import type { FieldHook } from 'payload'
import type { Post } from '@/payload-types'
// Hook for a text field in a Post collection
type PostTitleHook = FieldHook
const slugifyTitle: PostTitleHook = ({
value,
data,
siblingData,
originalDoc,
}) => {
// value is typed as string | undefined
// data is typed as Partial
// siblingData is typed as Partial
// originalDoc is typed as Post | undefined
// Generate slug from title if not provided
if (!siblingData.slug && value) {
const slug = value
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
return slug
}
return value
}
// Hook for a relationship field
type PostAuthorHook = FieldHook
const setDefaultAuthor: PostAuthorHook = ({ value, req }) => {
// value is typed as string | number | undefined
// Set current user as author if not provided
if (!value && req.user) {
return req.user.id
}
return value
}
```
> **Tip:** When defining field hooks, use the three generic parameters for full
> type safety: document type, field value type, and sibling data type. This
> provides autocomplete and type checking for all hook arguments.
# 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](/docs/v3/access-control/overview.md) and [Hooks](/docs/v3/hooks/overview.md)
## 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](/docs/v3/hooks/overview.md), [Access Control](/docs/v3/access-control/overview.md), [Validation](/docs/v3/fields/overview.md#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](/docs/v3/local-api/outside-nextjs.md).
## Local options available
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
| Local Option | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
| `data` | The data to use within the operation. Required for `create`, `update`. |
| `depth` | [Control auto-population](/docs/v3/queries/depth.md) of nested relationship and upload fields. |
| `locale` | Specify [locale](/docs/v3/configuration/localization.md) for any returned documents. |
| `select` | Specify [select](/docs/v3/queries/select.md) to control which fields to include to the result. |
| `populate` | Specify [populate](/docs/v3/queries/select.md#populate) to control which fields to include to the result from populated documents. |
| `fallbackLocale` | Specify a [fallback locale](/docs/v3/configuration/localization.md) 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](/docs/v3/admin/locked-documents.md). |
| `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 disable pagination calculations and document count queries. Payload still applies any positive `limit`. |
| `context` | [Context](/docs/v3/hooks/context.md), 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](/docs/v3/database/transactions.md) 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,
})
```
> **Note**
>
> `pagination`, `page`, and `limit` are three related properties [documented
> here](/docs/v3/queries/pagination.md).
### 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`](/docs/v3/authentication/overview.md) 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](/docs/v3/typescript/generating-types.md).
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](/docs/v3/local-api/overview.md) 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
import { UnauthorizedError } from 'payload'
export async function deletePost(postId, user) {
if (!user || user.role !== 'admin') {
throw new UnauthorizedError()
}
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
> **Note**
>
> 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`).
For example, if you have a Collection called `pages`, you can fetch its documents directly from the browser or any HTTP client:
```ts
fetch('https://localhost:3000/api/pages') // highlight-line
.then((res) => res.json())
.then((data) => console.log(data))
```
To enhance DX, you can use [Payload SDK](#payload-rest-api-sdk) to query your REST API.
**REST query parameters:**
- [depth](/docs/v3/queries/depth.md) - automatically populates relationships and uploads
- [locale](/docs/v3/configuration/localization.md#retrieving-localized-docs) - retrieves document(s) in a specific locale
- [fallback-locale](/docs/v3/configuration/localization.md#retrieving-localized-docs) - specifies a fallback locale if no locale value exists
- [select](/docs/v3/queries/select.md) - specifies which fields to include in the result
- [populate](/docs/v3/queries/select.md#populate) - specifies which fields to include in the result from populated documents
- [limit](/docs/v3/queries/pagination.md#options) - limits the number of documents returned
- [page](/docs/v3/queries/pagination.md#options) - specifies which page to get documents from when used with a limit
- [sort](/docs/v3/queries/sort.md#rest-api) - specifies the field(s) to use to sort the returned documents by
- [where](/docs/v3/queries/overview.md) - specifies advanced filters to use to query documents
- [joins](/docs/v3/fields/join.md#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](/docs/v3/admin/preferences.md) 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.
> **Warning**
>
> 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`. |
| **`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](/docs/v3/access-control/overview.md) and
> [Hooks](/docs/v3/hooks/overview.md).
#### Root-level routes
Custom endpoints defined in your Payload Config are always mounted under your configured `routes.api` path (default: `/api`). To define a route that is not prefixed by this path, add a Next.js [Route Handler](https://nextjs.org/docs/app/api-reference/file-conventions/route) at the desired location in your app directory:
```ts
// src/app/my-route/route.ts
export const GET = async () => {
return Response.json({
message: 'This is an example of a custom route.',
})
}
```
#### 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](/docs/v3/local-api/overview.md).
> **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'
const sdk = new PayloadSDK({
baseURL: 'https://example.com/api',
})
```
For projects without a `payload-types.ts` file, or when working with multiple Payload configs, you can manually pass the types as a generic:
```ts
import { PayloadSDK } from '@payloadcms/sdk'
import type { Config } from './payload-types'
const sdk = new PayloadSDK({
baseURL: 'https://example.com/api',
})
```
### Operations
```ts
// 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 config from '@payload-config'
import {
REST_DELETE,
REST_GET,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'
import { PayloadSDK } from '@payloadcms/sdk'
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 Clients
You can use any GraphQL client with Payload's GraphQL endpoint. Here are a few popular 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
## GraphQL Options
At the top of your Payload Config you can define all the options to manage GraphQL.
| Option | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/v3/graphql/extending.md) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/v3/graphql/extending.md) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/v3/graphql/overview.md#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground in production environments, defaults to true. [More](/docs/v3/graphql/overview.md#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](/docs/v3/admin/preferences.md) for the [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/production/preventing-abuse.md#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'
```
> **Warning**
>
> 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
> **Warning**
>
> **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](/docs/v3/configuration/collections.md). 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](/docs/v3/local-api/overview.md) - Extremely fast, direct-to-database access
- [REST API](/docs/v3/rest-api/overview.md) - Standard HTTP endpoints for querying and mutating data
- [GraphQL](/docs/v3/graphql/overview.md) - 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](/docs/v3/access-control/overview.md) 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](/docs/v3/fields/point.md) comma separated as `, , , `. |
| `within` | For [Point Fields](/docs/v3/fields/point.md) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](/docs/v3/fields/point.md#querying-within) |
| `intersects` | For [Point Fields](/docs/v3/fields/point.md) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](/docs/v3/fields/point.md#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](/docs/v3/database/indexes.md).
### 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 an `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](/docs/v3/local-api/overview.md) 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](/docs/v3/graphql/overview.md) 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](/docs/v3/rest-api/overview.md), 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.
> **Success**
>
> For more performance tips, see the [Performance
> documentation](/docs/v3/performance/overview.md).
### Indexes
Build [Indexes](/docs/v3/database/indexes.md) 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](/docs/v3/database/indexes.md).
### Depth
Set the [Depth](/docs/v3/queries/depth.md) 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](/docs/v3/queries/depth.md).
### Limit
Set the [Limit](/docs/v3/queries/pagination.md#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](/docs/v3/queries/pagination.md#limit).
### Select
Use the [Select API](/docs/v3/queries/select.md) 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](/docs/v3/queries/select.md).
### Pagination
[Disable Pagination](/docs/v3/queries/pagination.md#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](/docs/v3/queries/pagination.md).
# Sort
Source: https://payloadcms.com/docs/queries/sort
Documents in Payload can be easily sorted by a specific [Field](/docs/v3/fields/overview.md). 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](/docs/v3/fields/overview.md#string-path-virtual-fields). 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](/docs/v3/database/indexes.md).
## Local API
To sort Documents in the [Local API](/docs/v3/local-api/overview.md), 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](/docs/v3/rest-api/overview.md), 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))
```
You can also pass `sort` as an array so each item is a single field when using query string builder:
```ts
import { stringify } from 'qs-esm'
const getPosts = async () => {
const stringifiedQuery = stringify(
{
sort: ['priority', '-createdAt'],
},
{ addQueryPrefix: true },
)
const response = await fetch(
`https://localhost:3000/api/posts${stringifiedQuery}`, // highlight-line
)
const data = await response.json()
return data
}
```
## GraphQL API
To sort in the [GraphQL API](/docs/v3/graphql/overview.md), you can use the `sort` parameter in your query:
```ts
query {
Posts(sort: "-createdAt") {
docs {
color
}
}
}
```
To sort by multiple fields in GraphQL, pass a comma-separated list:
```ts
query {
Posts(sort: "priority,-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](/docs/v3/configuration/collections.md) as well as [Globals](/docs/v3/configuration/globals.md). 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](/docs/v3/graphql/overview.md),
> because there, depth is based on the shape of your queries.
## Local API
To specify depth in the [Local API](/docs/v3/local-api/overview.md), 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](/docs/v3/configuration/globals.md) using
> the `findGlobal` operation.
## REST API
To specify depth in the [REST API](/docs/v3/rest-api/overview.md), 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](/docs/v3/configuration/globals.md) 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](/docs/v3/fields/relationship.md) or the [Upload Field](/docs/v3/fields/upload.md) 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](/docs/v3/local-api/overview.md), you can use the `select` option in your query:
```ts
import type { Payload } from 'payload'
// Include mode - result type will only contain: id, text, group.number, and array
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 - result type will contain all fields except: array and group.number
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
}
```
### Empty select
The `id` field is always included in the result, regardless of your `select` query. If you pass an empty `select` object, only the `id` will be returned:
```ts
const post = await payload.findByID({
collection: 'posts',
id: '1',
select: {},
})
console.log(post) // { id: '1' }
```
> **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](/docs/v3/rest-api/overview.md), 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](/docs/v3/configuration/globals.md) 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](/docs/v3/fields/relationship.md) or [Upload](/docs/v3/fields/upload.md) 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](/docs/v3/fields/upload.md) 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` when pagination is enabled | Limits the number of documents returned per page. [More details](#limit). |
| `pagination` | `true` | Set to `false` to disable pagination calculations and document count queries. This respects any specified limit. |
| `page` | `1` | Get a specific page number. |
## Local API
To specify pagination controls in the [Local API](/docs/v3/local-api/overview.md), 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](/docs/v3/rest-api/overview.md), 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 calculations and document count queries by passing `pagination: false` to the `find` local operation. If you do not specify a `limit`, Payload returns all matching documents. If you specify a positive `limit`, Payload still applies that limit.
You can also set `limit: 0` to return all matching documents and disable pagination automatically.
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](/docs/v3/typescript/overview.md) 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](/docs/v3/live-preview/overview.md), [preview drafts](/docs/v3/admin/preview.md), [diff versions](/docs/v3/versions/overview.md), 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](/docs/v3/custom-components/overview.md)—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](/docs/v3/local-api/overview.md) on the front-end. You can install Payload into any [existing Next.js app in just one line](/docs/v3/getting-started/installation.md) and [deploy it anywhere](/docs/v3/production/deployment.md).
> **Success**
>
> The Payload Admin Panel is designed to be as minimal and straightforward as
> possible to allow easy customization and control. [Learn
> more](/docs/v3/custom-components/overview.md).

_Redesigned Admin Panel with a collapsible sidebar that's open by default, providing greater extensibility and enhanced horizontal real estate._
## Project Structure
The Admin Panel serves as the entire HTTP layer for Payload, providing a full CRUD interface for your app. This means that both the [REST](/docs/v3/rest-api/overview.md) and [GraphQL](/docs/v3/graphql/overview.md) 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](/docs/v3/getting-started/installation.md), 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
```
> **Note**
>
> 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 contain all the _routes_ related to the [REST API](/docs/v3/rest-api/overview.md) and [GraphQL API](/docs/v3/graphql/overview.md). All admin routes are [easily configurable](#customizing-routes) to meet your application's exact requirements.
> **Tip:** The `(payload)` folder location is flexible. You can move it to match
> your application's structure by updating the `routes` and `admin.importMap`
> configuration. See [Customizing Admin Panel Location](/docs/v3/admin/admin-panel-location.md)
> for step-by-step instructions.
> **Note:** If you don't intend to use the Admin Panel, [REST
> API](/docs/v3/rest-api/overview.md), or [GraphQL API](/docs/v3/graphql/overview.md), 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](/docs/v3/admin/customizing-css.md).
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](/docs/v3/configuration/overview.md) 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](/docs/v3/authentication/overview.md). |
| `autoRefresh` | Used to automatically refresh user tokens for users logged into the dashboard. [More details](/docs/v3/authentication/overview.md). |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](/docs/v3/custom-components/overview.md). |
| `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](/docs/v3/live-preview/overview.md). |
| `meta` | Base metadata to use for the Admin Panel. [More details](/docs/v3/admin/metadata.md). |
| `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](#toast) |
| `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](/docs/v3/configuration/collections.md#admin-options) and [Global Admin
> Options](/docs/v3/configuration/globals.md#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](/docs/v3/authentication/overview.md) 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](/docs/v3/authentication/overview.md). 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](/docs/v3/access-control/overview.md) 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](/docs/v3/access-control/overview.md) 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/3.x/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](/docs/v3/rest-api/overview.md), 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](/docs/v3/rest-api/overview.md) and [GraphQL API](/docs/v3/graphql/overview.md), or the root path of the Admin Panel itself.
To customize root-level routes, use the `routes` property in your [Payload Config](/docs/v3/configuration/overview.md):
```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](/docs/v3/rest-api/overview.md) base path. |
| `graphQL` | `/graphql` | The [GraphQL API](/docs/v3/graphql/overview.md) 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](/docs/v3/rest-api/overview.md#custom-endpoints) and [Custom
> Views](/docs/v3/custom-components/custom-views.md).
#### 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.
> **Advanced Routing:** For complex scenarios like nesting the admin panel at
> custom paths (e.g., `/admin/content`) or organizing multiple admin dashboards,
> see [Customizing Admin Panel Location](/docs/v3/admin/admin-panel-location.md).
### 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/custom-components/custom-views.md) for more information.
## I18n
The Payload Admin Panel is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/3.x/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](/docs/v3/configuration/i18n.md) 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.
Dates in Payload are always stored in UTC in the database. The timezone settings in the Admin Panel affect only how dates are displayed to editors to help ensure consistency for multi-region editorial teams.
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`. Also supports a function that is given the defaultTimezones list. |
| `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')`.
> **Note**
>
> **Important** You must enable timezones on each individual date field via
> `timezone: true`. See [Date Fields](/docs/v3/fields/date.md) 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',
},
},
})
```
### Extending supported timezones
For `supportedTimezones` we also support using a function that is given the defaultTimezones list. This allows you to easily extend the default list of timezones rather than replacing it completely.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
timezones: {
supportedTimezones: ({ defaultTimezones }) => [
...defaultTimezones, // list provided by Payload
{
label: 'Europe/Dublin',
value: 'Europe/Dublin',
},
{
label: 'Europe/Amsterdam',
value: 'Europe/Amsterdam',
},
{
label: 'Europe/Bucharest',
value: 'Europe/Bucharest',
},
],
defaultTimezone: 'Europe/Amsterdam',
},
},
})
```
### Using a UTC timezone
In some situations you may want the displayed date and time to match exactly what's being stored in the database where we always store values in UTC. You can do this by adding UTC as a valid timezone option.
Using a UTC timezone means that an editor inputing for example '1pm' will always see '1pm' and the stored value will be '13:00:00Z'.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
timezones: {
supportedTimezones: [
{
label: 'UTC',
value: 'UTC',
},
// ...other timezones
],
defaultTimezone: 'UTC',
},
},
})
```
## 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` |
| `position` | The position of the toast on the screen. | `bottom-right` |
## Frequently Asked Questions
### Can I move the Payload admin panel to a custom location?
Yes! Payload is flexible about where the admin panel lives. You can move it to custom routes like `/dashboard`, `/cms`, or nested paths like `/admin/content`. See [Customizing Admin Panel Location](/docs/v3/admin/admin-panel-location.md) for step-by-step instructions.
### Which auto-generated files can I safely modify?
Most files in the `(payload)` folder are only created once and can be safely modified. Only the `importMap.js` file is regenerated automatically. See [Understanding Auto-Generated Files](/docs/v3/admin/admin-panel-location.md#understanding-auto-generated-files) for a complete reference table.
### Why does layout.tsx say "DO NOT MODIFY" if I need to edit it?
This warning is misleading. The `layout.tsx` file was generated during initial project setup but is never regenerated by Payload. It is safe to modify, especially when customizing your folder structure. See [About the "DO NOT MODIFY" Warning](/docs/v3/admin/admin-panel-location.md#about-the-do-not-modify-warning) for details.
### What happens if I edit the import map file directly?
Don't do this - it will be overwritten on the next startup or HMR. Instead, configure component paths in your Payload config and let Payload regenerate the import map automatically.
# 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](/docs/v3/admin/overview.md) 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](/docs/v3/live-preview/overview.md).
> 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](/docs/v3/configuration/collections.md#admin-options) or [Global Config](/docs/v3/configuration/globals.md#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 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 }) => {
const encodedParams = new URLSearchParams({
slug,
collection: 'pages',
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 { 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 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) {
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 out the official [Draft
> Preview
> Example](https://github.com/payloadcms/payload/tree/3.x/examples/draft-preview)
> in the [Examples
> Directory](https://github.com/payloadcms/payload/tree/3.x/examples).
### Conditional Preview URLs
You can also conditionally enable or disable the preview button based on the document's data. This is useful for scenarios where you only want to show the preview button when certain criteria are met.
To do this, simply return `null` from the `preview` function when you want to hide the preview button:
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: (doc) => {
return doc?.enabled ? `http://localhost:3000/${doc.slug}` : null
},
},
fields: [
{
name: 'slug',
type: 'text',
},
{
name: 'enabled',
type: 'checkbox',
},
],
}
```
# Custom Admin Panel Location
Source: https://payloadcms.com/docs/admin/admin-panel-location
Payload is flexible about where your Admin Panel lives within your Next.js application. You can customize routes, move folders, and organize your project structure to match your application's needs.
## Common Use Cases
- **Custom admin routes** - Change `/admin` to `/dashboard`, `/cms`, etc.
- **Nested admin panels** - Place Payload at `/admin/content` alongside custom admin routes
- **Multiple admin interfaces** - Organize Payload and custom dashboards together
- **Organizational preferences** - Match your team's folder structure conventions
## Understanding the Payload Folder Structure
By default, Payload creates this structure in your Next.js app:
```
app/ (or src/app/)
├── (payload)/
│ ├── admin/
│ │ ├── [[...segments]]/
│ │ │ ├── not-found.tsx
│ │ │ └── page.tsx
│ │ └── importMap.js
│ ├── api/
│ │ └── [...slug]/
│ │ └── route.ts
│ ├── custom.scss
│ └── layout.tsx
```
- **`(payload)/`** - Parent folder containing all Payload-related routes
- **`admin/`** - Admin Panel UI routes
- **`api/`** - REST API routes
- **`layout.tsx`** - Root layout that imports the import map
- **`importMap.js`** - Auto-generated file mapping component paths (regenerated on startup)
## Scenario 1: Simple Route Change
To change the admin route from `/admin` to `/dashboard`:
**1. Update your Payload Config:**
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
routes: {
admin: '/dashboard', // Changed from '/admin'
},
})
```
**2. Move the folder:**
```bash
# Move the admin folder
mv app/(payload)/admin app/(payload)/dashboard
```
**3. Update the import path in `layout.tsx`:**
Edit `app/(payload)/layout.tsx` and update the import map reference to match the new folder name:
```ts
// Before
import { importMap } from './admin/importMap.js'
// After
import { importMap } from './dashboard/importMap.js'
```
> **Note**
>
> Your IDE may auto-update this import when you move the folder, but it may not.
> To catch any stale references to the old path, run a build or type-check
> (`pnpm build` or `pnpm tsc --noEmit`) and fix any import errors until it
> passes.
**4. Restart your dev server**
Payload will now be available at `/dashboard`.
## Scenario 2: Moving the Entire Payload Folder (Admin Panel and API)
To move both the Payload Admin Panel and API under a custom path, you move the entire `(payload)` folder. This example places everything under `/admin/content`.
> **Note**
>
> Because the `admin/` subfolder inside `(payload)` is a Next.js route segment,
> moving `(payload)` to `app/admin/content/(payload)` results in the admin panel
> being served at `/admin/content/admin`, not `/admin/content`. Keep this in
> mind when choosing your target path and setting `routes.admin`.
**1. Update your Payload Config with all routes and import map settings:**
```ts
import { buildConfig } from 'payload'
import path from 'path'
import { fileURLToPath } from 'url'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
// ...
routes: {
admin: '/admin/content/admin',
api: '/admin/content/api',
graphQL: '/admin/content/api/graphql',
graphQLPlayground: '/admin/content/api/graphql-playground',
},
admin: {
importMap: {
baseDir: path.resolve(dirname, './src/app/admin/content/(payload)'),
importMapFile: path.resolve(
dirname,
'./src/app/admin/content/(payload)/admin/importMap.js',
),
},
},
})
```
**2. Move the `(payload)` folder:**
```bash
# Create the new directory structure
mkdir -p app/admin/content
# Move the entire (payload) folder
mv app/(payload) app/admin/content/(payload)
```
**3. Update the import path in `layout.tsx`:**
Edit `app/admin/content/(payload)/layout.tsx`:
```ts
// Change this import path to match your new structure
import { importMap } from './admin/importMap.js'
```
The import path should be relative from `layout.tsx` to `importMap.js`. In this case, it remains `'./admin/importMap.js'`.
**4. Regenerate the import map:**
```bash
pnpm payload generate:importmap
```
Your Payload admin is now at `/admin/content/admin` and the API at `/admin/content/api`.
## Scenario 3: Multiple Admin Dashboards
If you want both Payload and custom admin routes under `/admin`:
**Folder Structure:**
```
app/
├── admin/
│ ├── content/ # Payload admin
│ │ └── (payload)/
│ │ ├── admin/
│ │ ├── api/
│ │ └── layout.tsx
│ ├── platform/ # Your custom admin
│ │ ├── dashboard/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ └── page.tsx # Choose between admins
```
**Configuration:**
```ts
export default buildConfig({
routes: {
admin: '/admin/content',
api: '/admin/content/api',
graphQL: '/admin/content/api/graphql',
graphQLPlayground: '/admin/content/api/graphql-playground',
},
admin: {
importMap: {
baseDir: path.resolve(dirname, './src/app/admin/content/(payload)'),
importMapFile: path.resolve(
dirname,
'./src/app/admin/content/(payload)/admin/importMap.js',
),
},
},
})
```
## Understanding Auto-Generated Files
Not all files in the `(payload)` folder are auto-generated. Here's what you need to know:
| File | Auto-Generated? | Safe to Edit? | When Regenerated? | Notes |
| ------------------------------------- | --------------- | ------------- | ---------------------------- | ------------------------------------------------------------ |
| `layout.tsx` | No | Yes | Never | Created once during setup. Safe to modify import paths. |
| `admin/importMap.js` | Yes | No | Startup, HMR, manual command | Always regenerated. Configure via `admin.importMap` instead. |
| `admin/[[...segments]]/page.tsx` | No | Rarely needed | Never | Part of template. Usually no need to edit. |
| `admin/[[...segments]]/not-found.tsx` | No | Rarely needed | Never | Part of template. Usually no need to edit. |
| `api/[...slug]/route.ts` | No | Rarely needed | Never | Part of template. Usually no need to edit. |
| `custom.scss` | No | Yes | Never | Intended for your custom styles. |
### About the "DO NOT MODIFY" Warning
You may see this warning in `layout.tsx`:
```ts
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
```
**This warning is misleading.** The file was generated during initial project setup by `create-payload-app`, but it is **never regenerated** by Payload. It is completely safe to modify this file, especially when customizing your folder structure.
## Editing layout.tsx Safely
When you move the Payload folder, you'll need to update the import path in `layout.tsx`:
```tsx
// app/admin/content/(payload)/layout.tsx
import { importMap } from './admin/importMap.js' // Update this path as needed
```
**What you can safely edit:**
- Import map path (required when moving folders)
- Custom imports for additional functionality
- Custom SCSS imports
- Server function logic (advanced use cases)
**What you should NOT edit:**
- The core `RootLayout` usage (required for Payload)
- The `serverFunction` pattern (required for Payload to work)
- The import map prop to `RootLayout`
## Import Map Regeneration
The `importMap.js` file is automatically regenerated in these scenarios:
- **Application startup** - Every time you start your dev server or production build
- **Hot Module Replacement (HMR)** - When you save changes to component files in development
- **Manual generation** - When you run `pnpm payload generate:importmap`
The import map is **never regenerated** during:
- Normal runtime (only at startup)
- After the production build completes
> **Important:** Never manually edit the `importMap.js` file. Configure
> component paths in your Payload config and let Payload regenerate the import
> map automatically.
## Troubleshooting
### Import map not found error
If you see an error about the import map not being found:
1. Verify `admin.importMap.importMapFile` points to the correct location
2. Run `pnpm payload generate:importmap` manually
3. Check that the path is absolute, not relative
### Layout.tsx import error
If you see an import error in `layout.tsx`:
1. Verify the import path is relative from `layout.tsx` to `importMap.js`
2. Use `./admin/importMap.js` if importMap is in a sibling `admin` folder
3. Use `./importMap.js` if you moved importMap to the same folder as layout
### Admin panel 404 error
If the admin panel returns 404:
1. Verify `routes.admin` matches your actual folder structure
2. Ensure you moved the entire `(payload)` folder, not just `admin`
3. Check that Next.js is recognizing your new routes
### Components not loading
If custom components aren't loading:
1. Verify `admin.importMap.baseDir` is correct
2. Regenerate the import map: `pnpm payload generate:importmap`
3. Check component paths in your config are relative to `baseDir`
## Additional Resources
- [Admin Panel Overview](/docs/v3/admin/overview.md)
- [Custom Components](/docs/v3/custom-components/overview.md)
- [Import Map Configuration](/docs/v3/custom-components/overview.md#import-map)
- [Customizing Routes](/docs/v3/admin/overview.md#customizing-routes)
> **Success**
>
> **Need Help?** If you're still having issues, check the [Community
> Help](https://payloadcms.com/community-help) or [open a new
> issue](https://github.com/payloadcms/payload/issues/new/choose).
# 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.
# Accessibility
Source: https://payloadcms.com/docs/admin/accessibility
Payload is committed to ensuring that our admin panel is accessible to all users, including those with disabilities. We follow best practices and guidelines to create an inclusive experience.
> **Note**
>
> We are actively working towards full compliance with WCAG 2.2 AA standards. If
> you encounter any accessibility issues, please report them in our [GitHub
> Discussion](https://github.com/payloadcms/payload/discussions/14489) page.
## Compliance standards
| Standard | Status | Description |
| -------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| [WCAG 2.2 AA](https://www.w3.org/TR/WCAG22/) | In Progress | Web Content Accessibility Guidelines (WCAG) 2.2 AA is a widely recognized standard for web accessibility. |
You can view our [report](https://github.com/payloadcms/payload/discussions/14489) on the current state of the admin panel's accessibility compliance.
## Our approach
- Integrated Axe within our e2e test suites to ensure long term compliance.
- Custom utilities to test keyboard navigation, window overflow and focus indicators across our components.
- Manual testing with screen readers and other assistive technologies.
# Customizing CSS & SCSS
Source: https://payloadcms.com/docs/admin/customizing-css
Customizing the Payload [Admin Panel](/docs/v3/admin/overview.md) 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](/docs/v3/admin/overview.md). 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](/docs/v3/custom-components/overview.md), 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/3.x/packages/ui/src/scss/queries.scss)
- [Colors](https://github.com/payloadcms/payload/blob/3.x/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/3.x/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/3.x/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](/docs/v3/admin/overview.md). 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](/docs/v3/custom-components/overview.md), such as [Custom Fields](/docs/v3/fields/overview.md#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](/docs/v3/custom-components/overview.md#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](/docs/v3/fields/overview.md#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](/docs/v3/fields/overview.md#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
}
```
### Updating Lexical Rich Text Fields
The Lexical editor uses `initialValue` — not `value` — as the trigger to re-mount and visually update its content. This is intentional: it prevents unnecessary re-renders on every keystroke. However, it means that calling `setValue` alone will update the form's `value` (and save correctly to the database), but **the editor UI will not reflect the change** until the page is refreshed.
To programmatically update a Lexical rich text field and have the editor visually reflect the new content, you must update both `value` and `initialValue` simultaneously. The way to do this is via `dispatchFields` with an `UPDATE` action, which can set any property of a field's form state at once:
```tsx
'use client'
import { useAllFormFields } from '@payloadcms/ui'
export const MyComponent: React.FC = () => {
const [, dispatchFields] = useAllFormFields()
const updateRichTextField = (newValue) => {
dispatchFields({
type: 'UPDATE',
path: 'myRichTextField', // the field's path in the form
value: newValue,
initialValue: newValue, // required to trigger Lexical re-render
})
}
return (
)
}
```
> **Warning**
>
> Using `setValue` from `useField` on a Lexical rich text field will update the
> form data (and persist correctly on save), but the editor UI will not visually
> update. Use `dispatchFields` with both `value` and `initialValue` set to the
> new content to trigger a re-render of the editor.
## 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.
> **Success**
>
> **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}
}
}
```
> **Warning**
>
> Be aware: in the example above, `MyComponent` may re-render if an ancestor
> component re-renders or if any of its props change (standard React behavior),
> but not because any fields other than `amount` or `feePercentage` changed.
## 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/3.x/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](/docs/v3/admin/locked-documents.md). |
| **`getDocPermissions`** | Method to retrieve document-level permissions. |
| **`getDocPreferences`** | Method to retrieve document-level user preferences. [More details](/docs/v3/admin/preferences.md). |
| **`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](/docs/v3/admin/locked-documents.md). |
| **`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](/docs/v3/admin/preferences.md). |
| **`data`** | The saved data of the document. |
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](/docs/v3/admin/preferences.md). |
| **`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](/docs/v3/admin/locked-documents.md). |
| **`unpublishedVersionCount`** | The number of unpublished versions of the document. |
| **`updateDocumentEditor`** | Method to update who is currently editing the document. [More details](/docs/v3/admin/locked-documents.md). |
| **`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](/docs/v3/query-presets/overview.md). |
| **`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](/docs/v3/custom-components/overview.md#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](/docs/v3/admin/preferences.md).
## 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)}
}
```
> **Note**
>
> 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](/docs/v3/admin/overview.md), 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](/docs/v3/configuration/localization.md)
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](/docs/v3/graphql/overview.md#preferences) and [REST](/docs/v3/rest-api/overview.md#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 is 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](/docs/v3/configuration/collections.md),
> [Global](/docs/v3/configuration/globals.md), 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](/docs/v3/custom-components/custom-views.md) 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](/docs/v3/admin/overview.md) 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](/docs/v3/configuration/overview.md).
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](/docs/v3/local-api/overview.md) 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](/docs/v3/custom-components/root-components.md)
- [Collection Components](/docs/v3/configuration/collections.md#custom-components)
- [Global Components](/docs/v3/configuration/globals.md#custom-components)
- [Field Components](/docs/v3/fields/overview.md#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, the `#` and export name can be omitted.
**Both default exports and named exports are fully supported.** Choose the pattern that works best for your codebase.
```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.
**Examples of component path syntax:**
```ts
// Named export using hash syntax
Button: '/components/Logout#MyComponent'
// Default export (no hash needed)
Button: '/components/Logout'
// Named export using exportName property
Button: {
path: '/components/Logout',
exportName: 'MyComponent',
}
```
### 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.
#### When Import Maps Are Regenerated
The import map file is automatically regenerated in these scenarios:
- **Application startup** - Every time you start your development server or build your application
- **Hot Module Replacement (HMR)** - When you save changes to component files during development
- **Manual generation** - When you run `payload generate:importmap` or `pnpm payload generate:importmap`
The import map is **never regenerated** during:
- Normal runtime (only at startup)
- After a production build completes
> **Important:** The import map file should never be manually edited. However,
> the `layout.tsx` file that imports it is safe to modify when customizing your
> folder structure. See [Customizing Admin Panel
> Location](/docs/v3/admin/admin-panel-location.md) for details.
#### 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/local-api/overview.md) 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`](/docs/v3/local-api/overview.md) class and the [`i18n`](/docs/v3/configuration/i18n.md) 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](/docs/v3/local-api/overview.md) class. |
| `i18n` | The [i18n](/docs/v3/configuration/i18n.md) object. |
> **Reminder:** All Custom Components also receive various other props that are
> specific to the component being rendered. See [Root
> Components](/docs/v3/custom-components/root-components.md), [Collection
> Components](/docs/v3/configuration/collections.md#custom-components), [Global
> Components](/docs/v3/configuration/globals.md#custom-components), or [Field
> Components](/docs/v3/fields/overview.md#custom-components) for a complete list of all
> default props per component.
#### Server principal vs. read-filtered user
Server Components that render an authenticated user receive two representations of that user, and choosing the wrong one either leaks data or breaks access checks:
- **`user`** (the top-level prop, e.g. on views and field components) is the authenticated user with **field read access applied** — read-restricted fields are removed and relationships are populated according to read access. Use it for anything passed toward Client Components or rendered to the page, so restricted data never reaches the client.
- **`req.user`** (from the `req` prop, or `initPageResult.req.user` in views) is the **complete server principal** with access bypassed. Use it for access-control checks, Local API queries, and role/authorization gating — `user` may omit fields your access logic relies on (for example a role stored in a read-restricted field).
> **Security:** Never pass `req.user` toward a Client Component or into client
> React state — use `user`. Conversely, never rely on `user` for authorization
> decisions — use `req.user`.
### 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](/docs/v3/configuration/overview.md) 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`](/docs/v3/admin/react-hooks.md#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
}
```
> **Success**
>
> See [Using Hooks](#using-hooks) for more details.
Similarly, all [Field Components](/docs/v3/fields/overview.md#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](/docs/v3/configuration/i18n.md). 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](/docs/v3/configuration/i18n.md). 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}
)
}
```
> **Success**
>
> See the [Hooks](/docs/v3/admin/react-hooks.md) documentation for a full list of
> available hooks.
### Getting the Current Locale
All [Custom Views](/docs/v3/custom-components/custom-views.md) can support multiple locales to be consistent with Payload's [Localization](/docs/v3/configuration/localization.md) 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]}
}
```
> **Success**
>
> See the [Hooks](/docs/v3/admin/react-hooks.md) 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](/docs/v3/admin/react-hooks.md) 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}`}
}
```
> **Success**
>
> See the [Hooks](/docs/v3/admin/react-hooks.md) documentation for a full list of
> available hooks.
### Adding Styles
Payload has a robust [CSS Library](/docs/v3/admin/customizing-css.md) 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](/docs/v3/admin/customizing-css.md).
## 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.
> **Success**
>
> For more performance tips, see the [Performance
> documentation](/docs/v3/performance/overview.md).
### 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`](/docs/v3/admin/react-hooks.md) 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])
// ...
}
```
### Imports best practices
When building Custom Components it's important to be mindful of your bundle sizes sent to the client which are primarily affected by your imports. Generally speaking you can import third party packages as needed though it's best to avoid large packages that bloat your bundle size.
The most common issue is importing from our `@payloadcms/ui` package in the wrong context. So here are the simple rules to follow:
- In the **admin panel UI** you always want to import everything from `@payloadcms/ui` to ensure there's no package mismatch issues.
- In the **frontend application** you must always import components and utilities from `@payloadcms/ui/path/to` for example `import { Button } from '@payloadcms/ui/elements/Button'` to ensure tree shaking is effective and your bundle sizes are minimized otherwise it will include the entire library with your frontend code and greatly bloat your bundle size.
> **Success**
>
> See the [Performance](/docs/v3/performance/overview.md) documentation for more tips and
> best practices.
### Troubleshooting
#### 'Assignment cannot be destructured' or 'value ... of useConfig is undefined'
There's a few variations of this error that hint at the same problem, sometimes it will error on `useConfig` hook or any other Payload UI hook like `useAuth`, `useLocale` with the error message that the value being destructured is `undefined`.
See [Troubleshooting Common Issues in Payload](/docs/v3/troubleshooting/troubleshooting.md#dependency-mismatches) for more details on resolving dependency mismatches.
> **Note**
>
> Generally we recommend pinning Payload packages to the exact same version in
> order to ensure that your package manager installs the same versions across
> all Payload packages.
# Root Components
Source: https://payloadcms.com/docs/custom-components/root-components
Root Components are those that affect the [Admin Panel](/docs/v3/admin/overview.md) at a high-level, such as the logo or the main nav. You can swap out these components with your own [Custom Components](/docs/v3/custom-components/overview.md) to create a completely custom look and feel.
When combined with [Custom CSS](/docs/v3/admin/customizing-css.md), 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/custom-components/custom-providers.md). |
| `views` | Override or create new views within the Admin Panel. [More details](/docs/v3/custom-components/custom-views.md). |
_For details on how to build Custom Components, see [Building Custom Components](/docs/v3/custom-components/overview.md#building-custom-components)._
> **Note:** You can also set [Collection
> Components](/docs/v3/configuration/collections.md#custom-components) and [Global
> Components](/docs/v3/configuration/globals.md#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](/docs/v3/custom-components/edit-view.md) and
> [List View](/docs/v3/custom-components/list-view.md) 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.
}
```
> **Note:** You can also set [Dashboard Widgets](/docs/v3/custom-components/dashboard.md)
> in the `admin.dashboard` property, or replace the entire [Dashboard
> View](/docs/v3/custom-components/dashboard.md) with your own.
### 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.
}
```
> **Note:** You can also set [Dashboard Widgets](/docs/v3/custom-components/dashboard.md)
> in the `admin.dashboard` property, or replace the entire [Dashboard
> View](/docs/v3/custom-components/dashboard.md) with your own.
### 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](/docs/v3/custom-components/overview.md) to your [Admin Panel](/docs/v3/admin/overview.md), 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/custom-components/overview.md#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](/docs/v3/admin/overview.md), such as the Dashboard, [List View](/docs/v3/custom-components/list-view.md), and [Edit View](/docs/v3/custom-components/edit-view.md). One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](/docs/v3/custom-components/overview.md) 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](/docs/v3/custom-components/document-views.md)
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](/docs/v3/configuration/overview.md). 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
},
},
},
})
```
> **Note:** The dashboard is a special case, where in addition to replacing the
> default view, [you can add widgets modularly](/docs/v3/custom-components/dashboard.md).
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](/docs/v3/admin/metadata.md). |
_\* An asterisk denotes that a property is required._
### Adding New Views
To add a _new_ view to the [Admin Panel](/docs/v3/admin/overview.md), 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](/docs/v3/custom-components/overview.md) 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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/overview.md#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](/docs/v3/custom-components/document-views.md). |
| `i18n` | The [i18n](/docs/v3/configuration/i18n.md) object. |
| `payload` | The [Payload](/docs/v3/local-api/overview.md) class. |
> **Note:** The props above apply to **Root Views** and any view added at the
> root level. Collection List Views and Collection/Global Edit Views receive a
> different set of props specific to their context. See [List View
> Props](/docs/v3/custom-components/list-view.md#props) and [Document Views](/docs/v3/custom-components/document-views.md) for their
> respective prop references.
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](#view-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,
user,
}: AdminViewServerProps) {
return (
Custom Default Root View
This view uses the Default Template.
)
}
```
The top-level `user` has field read access applied and should be used for values passed toward client-facing components, so restricted data never reaches the client. `initPageResult.req.user` retains the complete server-side identity and should be used for access-control checks and Local API queries — do not use it for values sent to the client, and do not rely on the filtered `user` for authorization decisions. See [Server principal vs. read-filtered user](/docs/v3/custom-components/overview.md#server-principal-vs-read-filtered-user) for details.
### 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](/docs/v3/admin/overview.md). 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](/docs/v3/configuration/overview.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/custom-components/document-views.md).
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](/docs/v3/custom-components/document-views.md). |
| `list` | The List View is used to show a list of Documents for any given Collection. [More details](/docs/v3/custom-components/list-view.md). |
| `[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](/docs/v3/configuration/globals.md):
```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](/docs/v3/custom-components/document-views.md).
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](/docs/v3/custom-components/document-views.md). |
| `[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)._
# Dashboard Widgets
Source: https://payloadcms.com/docs/custom-components/dashboard
> **Warning**
>
> This new Modular Dashboard is an experimental feature and may change in future
> releases. Use at your own risk.
The Dashboard is the first page users see when they log into the Payload Admin Panel. By default, it displays cards with the collections and globals. You can customize the dashboard by adding **widgets** - modular components that can display data, analytics, or any other content.
[Video: Create a flexible admin panel with the new Modular Dashboard feature](https://www.youtube.com/watch?v=fXF34Ef6G84)
One of the coolest things about widgets is that each plugin can define its own. Some examples:
- Analytics
- Error Reporting
- Number of documents that meet a certain filter
- Jobs recently executed
### Defining Widgets
Define widgets in your Payload config using the `admin.dashboard.widgets` property:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
admin: {
dashboard: {
widgets: [
{
slug: 'sales-summary',
Component: './components/SalesSummary.tsx#default',
fields: [
{ name: 'title', type: 'text' },
{
name: 'timeframe',
type: 'select',
options: ['daily', 'weekly', 'monthly', 'yearly'],
},
{ name: 'showTrend', type: 'checkbox' },
],
minWidth: 'small',
maxWidth: 'medium',
},
],
},
},
})
```
### Widget Configuration
| Property | Type | Description |
| -------------- | ------------- | -------------------------------------------------------------------- |
| `slug` \* | `string` | Unique identifier for the widget |
| `Component` \* | `string` | Path to the widget component (supports `#` syntax for named exports) |
| `fields` | `Field[]` | Optional widget-specific form fields shown in the edit drawer |
| `minWidth` | `WidgetWidth` | Minimum width the widget can be resized to (default: `'x-small'`) |
| `maxWidth` | `WidgetWidth` | Maximum width the widget can be resized to (default: `'full'`) |
**WidgetWidth Values:** `'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'full'`.
### Creating a Widget Component
Widgets are React Server Components that receive `WidgetServerProps`:
```tsx
import type { WidgetServerProps } from 'payload'
export default async function UserStatsWidget({ req }: WidgetServerProps) {
const { payload } = req
// Fetch data server-side
const userCount = await payload.count({ collection: 'users' })
return (
Total Users
{userCount.totalDocs}
)
}
```
For visual consistency with the Payload UI, we recommend:
1. Using the `card` class for your root element, unless you don't want it to have a background color.
2. Using our theme variables for backgrounds and text colors. For example, use `var(--theme-elevation-0)` for backgrounds and `var(--theme-text)` for text colors.
### Default Layout
Control the initial dashboard layout with the `defaultLayout` property:
```ts
export default buildConfig({
admin: {
dashboard: {
defaultLayout: ({ req }) => {
// Customize layout based on user role or other factors
const isAdmin = req.user?.roles?.includes('admin')
return [
{ widgetSlug: 'collections', width: 'full' },
{
widgetSlug: 'sales-summary',
data: {
timeframe: 'monthly',
title: 'Revenue Overview',
},
width: isAdmin ? 'medium' : 'small',
},
{ widgetSlug: 'user-stats', width: isAdmin ? 'medium' : 'full' },
{ widgetSlug: 'revenue-chart', width: 'full' },
]
},
widgets: [
// ... widget definitions
],
},
},
})
```
The `defaultLayout` function receives the request object and should return an array of `WidgetInstance` objects.
If your widget has `fields`, you can type `widgetData` with generated widget types:
```tsx
import type { WidgetServerProps } from 'payload'
import type { SalesSummaryWidget } from '../payload-types'
export default async function SalesSummaryWidgetComponent({
widgetData,
}: WidgetServerProps) {
const title = widgetData?.title ?? 'Sales Summary'
const timeframe = widgetData?.timeframe ?? 'monthly'
return (
{title} ({timeframe})
)
}
```
#### WidgetInstance Type
| Property | Type | Description |
| --------------- | ------------- | ---------------------------------------------------- |
| `widgetSlug` \* | `string` | Slug of the widget to display |
| `data` | `object` | Optional widget-specific data passed to `widgetData` |
| `width` | `WidgetWidth` | Initial width of the widget (default: minWidth) |
`width` is constrained by each widget's `minWidth` and `maxWidth` when types are generated.
> **Tip:** Users can customize their dashboard layout, which is saved to their
> preferences. The `defaultLayout` is only used for first-time visitors or after
> a layout reset.
### Built-in Widgets
Payload includes a built-in `collections` widget that displays collection and global cards.
If you don't define a `defaultLayout`, the collections widget will be automatically included in your dashboard.
### User Customization
Users can customize their dashboard by:
1. Clicking the dashboard dropdown in the breadcrumb
2. Selecting "Edit Dashboard"
3. Adding widgets via the "Add +" button
4. Editing widget data (for widgets with `fields`) via the edit button
5. Resizing widgets using the width dropdown on each widget (if multiple widths are allowed)
6. Reordering widgets via drag-and-drop
7. Deleting widgets using the delete button
8. Saving changes or canceling to revert
Users can also reset their dashboard to the default layout using the "Reset Layout" option.
# Document Views
Source: https://payloadcms.com/docs/custom-components/document-views
Document Views consist of multiple, individual views that together represent any single [Collection](/docs/v3/configuration/collections.md) or [Global](/docs/v3/configuration/globals.md) Document. All Document Views 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](/docs/v3/custom-components/edit-view.md) and API View, but you can also create [entirely new views](/docs/v3/custom-components/custom-views.md#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](/docs/v3/configuration/collections.md) or [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/custom-components/edit-view.md). |
| `versions` | The Versions View is used to navigate the version history of a single document. It is rendered within the "Versions" tab. [More details](/docs/v3/versions/overview.md). |
| `version` | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](/docs/v3/versions/overview.md). |
| `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](/docs/v3/live-preview/overview.md). |
| `[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](/docs/v3/custom-components/custom-views.md#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](/docs/v3/custom-components/edit-view.md), 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](/docs/v3/configuration/collections.md) or [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/custom-components/edit-view.md) 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](/docs/v3/configuration/collections.md) or [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/configuration/collections.md) and [Global](/docs/v3/configuration/globals.md) Documents within the [Admin Panel](/docs/v3/admin/overview.md). The Edit View contains the actual form that 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](/docs/v3/custom-components/document-views.md) in
> the Payload Admin Panel. Each Document View is responsible for a different
> aspect of interacting with a single Document.
## Custom Edit View
To swap out the entire Edit View with a [Custom View](/docs/v3/custom-components/custom-views.md), use the `views.edit.default` property in your [Collection Config](/docs/v3/configuration/collections.md) or [Global Config](/docs/v3/configuration/globals.md):
```tsx
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
fields: [],
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](/docs/v3/custom-components/custom-views.md#building-custom-views)._
## Custom Components
In addition to swapping out the entire Edit View with a [Custom View](/docs/v3/custom-components/custom-views.md), 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 and 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](/docs/v3/configuration/collections.md):
```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). |
| `UnpublishButton` | A button that unpublishes the current document. [More details](#unpublishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Collection. [More details](#description). |
| `Status` | A component that represents the status of the current document. [More details](#status). |
| `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](/docs/v3/configuration/globals.md):
```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). |
| `UnpublishButton` | A button that unpublishes the current document. [More details](#unpublishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Global. [More details](#description). |
| `Status` | A component that represents the status of the global. [More details](#status). |
### SaveButton
The `SaveButton` component allows you to render a custom Save Button in the Edit View.
#### Configuration Path
- **Collections:** `admin.components.edit.SaveButton`
- **Globals:** `admin.components.elements.SaveButton`
#### Example for Collections
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
components: {
edit: {
// highlight-start
SaveButton: '/path/to/CustomSaveButton',
// highlight-end
},
},
},
}
```
#### Example for Globals
```ts
import type { GlobalConfig } from 'payload'
export const Header: GlobalConfig = {
slug: 'header',
admin: {
components: {
elements: {
// highlight-start
SaveButton: '/path/to/CustomSaveButton',
// 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 your [Collection Config](/docs/v3/configuration/collections.md) or `components.elements.beforeDocumentControls` in your [Global Config](/docs/v3/configuration/globals.md):
#### 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](/docs/v3/configuration/collections.md):
#### 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 { EditMenuItemsClientProps } from 'payload'
export const EditMenuItems = (props: EditMenuItemsClientProps) => {
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` component allows you to render a custom Save Draft Button in the Edit View.
#### Configuration Path
- **Collections:** `admin.components.edit.SaveDraftButton`
- **Globals:** `admin.components.elements.SaveDraftButton`
#### Example for Collections
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
versions: {
drafts: true,
},
admin: {
components: {
edit: {
// highlight-start
SaveDraftButton: '/path/to/CustomSaveDraftButton',
// highlight-end
},
},
},
}
```
#### Example for Globals
```ts
import type { GlobalConfig } from 'payload'
export const Header: GlobalConfig = {
slug: 'header',
versions: {
drafts: true,
},
admin: {
components: {
elements: {
// highlight-start
SaveDraftButton: '/path/to/CustomSaveDraftButton',
// 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` component allows you to render a custom Publish Button in the Edit View.
#### Configuration Path
- **Collections:** `admin.components.edit.PublishButton`
- **Globals:** `admin.components.elements.PublishButton`
#### Example for Collections
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
versions: {
drafts: true,
},
admin: {
components: {
edit: {
// highlight-start
PublishButton: '/path/to/CustomPublishButton',
// highlight-end
},
},
},
}
```
#### Example for Globals
```ts
import type { GlobalConfig } from 'payload'
export const Header: GlobalConfig = {
slug: 'header',
versions: {
drafts: true,
},
admin: {
components: {
elements: {
// highlight-start
PublishButton: '/path/to/CustomPublishButton',
// 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 { PublishButtonServerProps } from 'payload'
export function MyPublishButton(props: PublishButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PublishButton } from '@payloadcms/ui'
export function MyPublishButton() {
return
}
```
### UnpublishButton
The `UnpublishButton` property allows you to render a custom Unpublish Button in the Edit View.
To add an `UnpublishButton` component, use the `components.edit.UnpublishButton` property in your [Collection Config](/docs/v3/configuration/collections.md) or `components.elements.UnpublishButton` in your [Global Config](/docs/v3/configuration/globals.md):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
UnpublishButton: '/path/to/MyUnpublishButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `UnpublishButton` component:
#### Server Component
```tsx
import React from 'react'
import { UnpublishButton } from '@payloadcms/ui'
import type { UnpublishButtonServerProps } from 'payload'
export function MyUnpublishButton(props: UnpublishButtonServerProps) {
return
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { UnpublishButton } from '@payloadcms/ui'
export function MyUnpublishButton() {
return
}
```
### PreviewButton
The `PreviewButton` component allows you to render a custom Preview Button in the Edit View.
#### Configuration Path
- **Collections:** `admin.components.edit.PreviewButton`
- **Globals:** `admin.components.elements.PreviewButton`
#### Example for Collections
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
preview: () => 'https://example.com/preview',
components: {
edit: {
// highlight-start
PreviewButton: '/path/to/CustomPreviewButton',
// highlight-end
},
},
},
}
```
#### Example for Globals
```ts
import type { GlobalConfig } from 'payload'
export const Header: GlobalConfig = {
slug: 'header',
admin: {
preview: () => 'https://example.com/preview',
components: {
elements: {
// highlight-start
PreviewButton: '/path/to/CustomPreviewButton',
// 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](/docs/v3/configuration/collections.md) or `components.elements.Description` in your [Global Config](/docs/v3/configuration/globals.md):
```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](/docs/v3/custom-components/list-view.md).
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)
}
```
### Status
The `Status` component displays the document's draft/published state when versioning with drafts is enabled.
#### Configuration Path
- **Collections:** `admin.components.edit.Status`
- **Globals:** `admin.components.elements.Status`
#### Example for Collections
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
versions: {
drafts: true,
},
admin: {
components: {
edit: {
// highlight-start
Status: '/path/to/CustomStatus',
// highlight-end
},
},
},
}
```
#### Example for Globals
```ts
import type { GlobalConfig } from 'payload'
export const Header: GlobalConfig = {
slug: 'header',
versions: {
drafts: true,
},
admin: {
components: {
elements: {
// highlight-start
Status: '/path/to/CustomStatus',
// highlight-end
},
},
},
}
```
### Upload
The `Upload` property allows you to render a custom file upload component in the Edit View. This is only available for upload-enabled Collections.
To add an `Upload` component, use the `components.edit.Upload` property in your [Collection Config](/docs/v3/configuration/collections.md):
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: true,
admin: {
components: {
edit: {
// highlight-start
Upload: '/path/to/MyUploadComponent#MyUploadServer',
// highlight-end
},
},
},
}
```
> **Important:** Custom upload components must integrate with Payload's form
> system to work correctly. You cannot use a simple `` as
> it won't connect to Payload's upload API. Instead, use Payload's built-in
> `` component from `@payloadcms/ui` or properly integrate with form
> hooks like `useDocumentInfo()`.
Here's an example of a custom `Upload` component:
#### Server Component
```tsx
import React from 'react'
import type {
PayloadServerReactComponent,
SanitizedCollectionConfig,
} from 'payload'
import { CustomUploadClient } from './MyUploadComponent.client'
export const MyUploadServer: PayloadServerReactComponent<
SanitizedCollectionConfig['admin']['components']['edit']['Upload']
> = (props) => {
return (
Custom Upload Interface
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { Upload, useDocumentInfo } from '@payloadcms/ui'
export const CustomUploadClient = () => {
const { collectionSlug, docConfig, initialState } = useDocumentInfo()
return (
)
}
```
For more details on customizing upload components, including examples with custom actions and drawers, see the [Upload documentation](/docs/v3/upload/overview.md#customizing-the-upload-ui).
# List View
Source: https://payloadcms.com/docs/custom-components/list-view
The List View is where users interact with a list of [Collection](/docs/v3/configuration/collections.md) Documents within the [Admin Panel](/docs/v3/admin/overview.md). 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](/docs/v3/configuration/collections.md) have a List View.
> [Globals](/docs/v3/configuration/globals.md) 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](/docs/v3/custom-components/custom-views.md), use the `admin.components.views.list` property in your [Collection Config](/docs/v3/configuration/collections.md):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
list: {
Component: '/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'
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)
}
```
### Props
Custom List Views receive a different set of props than other views because Payload pre-fetches data and pre-renders components before passing the results to your component. The exact props available depend on whether you are building a Server or Client Component.
#### Server Component Props (`ListViewServerProps`)
Server components receive all [client props](#client-component-props-listviewclientprops) plus the following additional server-only data:
| Prop | Description |
| ---------------------- | ------------------------------------------------------------------------------------- |
| `collectionConfig` | The sanitized [Collection Config](/docs/v3/configuration/collections.md) for this collection. |
| `data` | The paginated query result, including `docs`, `totalDocs`, `page`, `totalPages`, etc. |
| `limit` | The number of documents displayed per page. |
| `listPreferences` | Saved user preferences for this list (columns, sort, etc.). |
| `listSearchableFields` | The fields configured as searchable in the collection's admin options. |
| `i18n` | The [i18n](/docs/v3/configuration/i18n.md) object. |
| `locale` | The active locale, if localization is enabled. |
| `params` | Route parameters from the Next.js dynamic route. |
| `payload` | The [Payload](/docs/v3/local-api/overview.md) class. |
| `permissions` | The current user's permissions. |
| `searchParams` | URL search parameters (page, sort, where, limit, etc.). |
| `user` | The currently authenticated user. |
#### Client Component Props (`ListViewClientProps`)
| Prop | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| `collectionSlug` | The slug of the collection being displayed. |
| `columnState` | An array of column definitions describing the current table columns and their state. |
| `disableBulkDelete` | When `true`, hides the bulk delete action. |
| `disableBulkEdit` | When `true`, hides the bulk edit action. |
| `disableQueryPresets` | When `true`, disables the query presets feature. |
| `enableRowSelections` | When `true`, enables checkboxes on each row for bulk actions. |
| `hasCreatePermission` | Whether the current user has permission to create new documents. |
| `hasDeletePermission` | Whether the current user has permission to delete documents. |
| `hasTrashPermission` | Whether the current user has permission to use the trash. |
| `newDocumentURL` | The URL to navigate to when creating a new document. |
| `renderedFilters` | A `Map` of pre-rendered filter components, keyed by field path. |
| `resolvedFilterOptions` | A `Map` of resolved options for relationship filter fields. |
| `viewType` | The current view type identifier. |
| `Table` | A pre-rendered React node of the documents table. Pass this through to render the built-in table. |
| `AfterList` | Slot for components rendered after the list. |
| `AfterListTable` | Slot for components rendered after the table. |
| `BeforeList` | Slot for components rendered before the list. |
| `BeforeListTable` | Slot for components rendered before the table. |
| `Description` | Slot for the collection description component. |
| `listMenuItems` | Slot for custom items rendered in the list controls menu. |
> **Note:** Custom List Views are rendered inside a `ListQueryProvider`. This
> means any Client Component in the tree can call `useListQuery()` from
> `@payloadcms/ui` to read or update the current query state (search term, sort,
> page, filters, etc.).
### Using DefaultListView
If you want to keep Payload's built-in table and controls but add your own layout around them, import and render `DefaultListView` from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import type { ListViewClientProps } from 'payload'
import { DefaultListView } from '@payloadcms/ui'
export function MyCustomClientListView(props: ListViewClientProps) {
return (
My Custom Header
)
}
```
## Custom Components
In addition to swapping out the entire List View with a [Custom View](/docs/v3/custom-components/custom-views.md), 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](/docs/v3/configuration/collections.md):
### Slot Props
All slot components (`beforeList`, `beforeListTable`, `afterList`, `afterListTable`) receive the same base set of client props plus additional server-only props in Server Components:
**Client Props** (`BeforeListClientProps`, `AfterListClientProps`, etc.):
| Prop | Description |
| --------------------- | ---------------------------------------------------------------- |
| `collectionSlug` | The slug of the collection being displayed. |
| `hasCreatePermission` | Whether the current user has permission to create new documents. |
| `hasDeletePermission` | Whether the current user has permission to delete documents. |
| `hasTrashPermission` | Whether the current user has permission to use the trash. |
| `newDocumentURL` | The URL to navigate to when creating a new document. |
**Additional Server-Only Props** (`BeforeListServerProps`, `AfterListServerProps`, etc.):
| Prop | Description |
| ---------------------- | ------------------------------------------------------------------------------------- |
| `collectionConfig` | The sanitized [Collection Config](/docs/v3/configuration/collections.md) for this collection. |
| `data` | The paginated query result, including `docs`, `totalDocs`, `page`, `totalPages`, etc. |
| `limit` | The number of documents displayed per page. |
| `listPreferences` | Saved user preferences for this list (columns, sort, etc.). |
| `listSearchableFields` | The fields configured as searchable in the collection's admin options. |
| `i18n` | The [i18n](/docs/v3/configuration/i18n.md) object. |
| `payload` | The [Payload](/docs/v3/local-api/overview.md) class. |
| `permissions` | The current user's permissions. |
| `user` | The currently authenticated user. |
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/configuration/collections.md):
```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](/docs/v3/custom-components/edit-view.md).
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)
}
```
# UI Components
Source: https://payloadcms.com/docs/ui-components/overview
The `@payloadcms/ui` package contains the React components used throughout the Payload Admin Panel. You can use these components to build Custom Components that match the surrounding Admin Panel interface and behavior.
Each component guide includes working examples for the selected version of Payload, common props, accessibility guidance, and any providers or setup the component requires.
### Actions & navigation
Trigger actions and help users move through custom Admin Panel interfaces.
- [Button](/docs/v3/ui-components/button.md): Trigger actions with Payload's standard button styles.
- [CopyToClipboard](/docs/v3/ui-components/copy-to-clipboard.md): Copy text to the clipboard with built-in feedback.
- [Hamburger](/docs/v3/ui-components/hamburger.md): Render Payload's animated menu toggle.
- [Link](/docs/v3/ui-components/link.md): Navigate with Payload's router-aware link.
- [Navigation Controls](/docs/v3/ui-components/navigation-controls.md): Compose Admin menu and step navigation controls.
- [Step Navigation](/docs/v3/ui-components/step-navigation.md): Set the Admin header breadcrumbs from a Custom View.
### Inputs & selection
Collect, edit, search, and select values with Admin-styled controls.
- [CodeEditor](/docs/v3/ui-components/code-editor.md): Display and edit code with Payload's Monaco-based editor.
- [DatePicker](/docs/v3/ui-components/date-picker.md): Select calendar dates and times.
- [Field Labels and Messages](/docs/v3/ui-components/field-labels-and-messages.md): Add labels, descriptions, and validation messages to custom fields.
- [Text Inputs](/docs/v3/ui-components/text-inputs.md): Build custom text and textarea field controls.
- [Checkbox and Select Inputs](/docs/v3/ui-components/checkbox-and-select-inputs.md): Collect boolean and option values with Payload field controls.
- [PillSelector](/docs/v3/ui-components/pill-selector.md): Choose from a compact group of pill-shaped options.
- [ReactSelect and Select](/docs/v3/ui-components/react-select.md): Add select inputs styled for the Payload Admin Panel.
- [Relationship and Upload Inputs](/docs/v3/ui-components/relationship-and-upload-inputs.md): Select related documents and media from custom fields.
- [SearchBar](/docs/v3/ui-components/search-filter.md): Filter content with a complete, debounced search control.
- [TimezonePicker](/docs/v3/ui-components/timezone-picker.md): Select from a configured list of timezones.
### Layout & structure
Arrange content and create expandable, responsive interface sections.
- [AnimateHeight](/docs/v3/ui-components/animate-height.md): Animate a container as its content changes height.
- [Card](/docs/v3/ui-components/card.md): Present content in a clickable or navigable card.
- [Collapsible](/docs/v3/ui-components/collapsible.md): Show and hide a section beneath a collapsible header.
- [Gutter](/docs/v3/ui-components/gutter.md): Align content with the Admin Panel's responsive gutters.
### Feedback & status
Communicate outcomes, validation, loading states, and contextual information.
- [Banner](/docs/v3/ui-components/banner.md): Display contextual notices, warnings, and errors.
- [ErrorPill](/docs/v3/ui-components/error-pill.md): Show a compact validation or error indicator.
- [Motion and Loading](/docs/v3/ui-components/motion-and-loading.md): Communicate loading and animate layout changes.
- [Pill](/docs/v3/ui-components/pill.md): Display compact metadata, statuses, and actions.
- [ShimmerEffect](/docs/v3/ui-components/shimmer-effect.md): Show a loading placeholder while content resolves.
### Data display & lists
Present structured data, media, and collection controls.
- [Icons](/docs/v3/ui-components/icons.md): Use Payload's interface icons in custom Admin Panel UI.
- [List and Pagination Controls](/docs/v3/ui-components/list-and-pagination-controls.md): Add pagination, sorting, and selection to Admin lists.
- [Table](/docs/v3/ui-components/table.md): Display rows with Payload's responsive table styles.
- [Table Cells](/docs/v3/ui-components/table-cells.md): Render field values and build purpose-specific table cells.
- [Thumbnail](/docs/v3/ui-components/thumbnail.md): Render media thumbnails with file-type fallbacks.
### Interaction & overlays
Build reorderable interfaces and contextual layers for focused interactions.
- [Draggable Sortable](/docs/v3/ui-components/draggable-sortable.md): Reorder custom interface content with pointer and keyboard controls.
- [Modals and Drawers](/docs/v3/ui-components/modals-and-drawers.md): Present focused actions and supporting content in overlays.
- [Popup and PopupList](/docs/v3/ui-components/popup.md): Build contextual menus and lists of actions.
- [Tooltip](/docs/v3/ui-components/tooltip.md): Provide contextual labels on hover or focus.
### Documents & uploads
Extend document editing and upload-enabled Collection workflows.
- [Document Actions](/docs/v3/ui-components/document-actions.md): Save, publish, and perform bulk operations with Admin-aware controls.
- [Document State](/docs/v3/ui-components/document-state.md): Handle document locks, takeovers, and unsaved changes.
- [Dropzone](/docs/v3/ui-components/dropzone.md): Accept files through drag, drop, and paste interactions.
- [Upload](/docs/v3/ui-components/upload.md): Extend upload-enabled Collection file workflows.
- [Upload Helpers](/docs/v3/ui-components/upload-helpers.md): Display file details, generated sizes, and image adjustments.
> **Warning**
>
> UI component APIs can change between major versions of Payload. Use the
> version selector to view examples that match the version installed in your
> project.
## Using UI components
Inside a Payload Admin Panel Custom Component, import components from `@payloadcms/ui`:
```tsx
'use client'
import { Button } from '@payloadcms/ui'
export function SaveButton() {
return
}
```
Some components depend on context provided by the Admin Panel. Each guide calls out those requirements when a component cannot be rendered independently.
# AnimateHeight
Source: https://payloadcms.com/docs/ui-components/animate-height
The `AnimateHeight` component animates its children between a collapsed height and their natural height. It is useful when building disclosure controls and expandable regions.
## Import
```tsx
import { AnimateHeight } from '@payloadcms/ui'
```
## Interactive example
**Implementation**
```tsx
const [isOpen, setIsOpen] = useState(true)
Expandable content
```
**Styling**
AnimateHeight controls movement rather than appearance. Style its content wrapper to give every expanded panel a project-specific surface.
```css
.rah-static > div {
background: #f3f1ff;
border-left: 3px solid #6d5dfc;
border-radius: 4px;
padding: 1rem;
}
```
- `background`: Expanded content surface.
- `border-left`: Accent edge for the expanded region.
- `padding`: Space around the animated content.
The control that changes `height` remains your responsibility. Give that control an accessible label and expose its expanded state with `aria-expanded` when it controls a disclosure region.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ------------- | ------------------ | ------- | ----------------------------------------------- |
| `children` \* | `ReactNode` | — | Content whose height is animated. |
| `height` | `number \| 'auto'` | — | Target height. Use `0` to collapse the content. |
| `duration` | `number` | `300` | Animation duration in milliseconds. |
| `id` | `string` | — | Sets the wrapper element ID. |
| `className` | `string` | — | Adds a class to the wrapper element. |
_\* An asterisk denotes that a prop is required._
# Banner
Source: https://payloadcms.com/docs/ui-components/banner
The `Banner` component displays a short message that needs more emphasis than surrounding content. Use its type to communicate the purpose of the message.
## Import
```tsx
import { Banner } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
Changes saved successfully.
```
**Styling**
Target a Banner type to replace its default surface without changing semantic success or error messages.
```css
.banner--type-default {
background: #f3f1ff;
color: #211b4d;
border: 1px solid #c8c0ff;
border-radius: 8px;
}
```
- `background`: Banner surface color.
- `border`: Optional Banner outline.
- `color`: Banner text and inherited icon color.
## Types
Use `success` to confirm an action, `error` when something failed, `warning` to flag a risk, and `info` for additional context. Use the default type for neutral messages.
**Implementation**
```tsx
Default messageChanges saved successfully.Additional context is available.Something went wrong.
```
**Styling**
Use each semantic modifier to align success and error Banners with a customer’s status palette.
```css
.banner--type-success {
background: #dcfce7;
color: #166534;
}
.banner--type-error {
background: #fee2e2;
color: #991b1b;
}
```
- `background`: Semantic Banner surface.
- `color`: Semantic Banner text and inherited icon color.
Keep banner copy concise and include the next action when the user must resolve something.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ----------- | ---------------------------------------------------------- | ----------- | ------------------------------------------- |
| `children` | `ReactNode` | — | Message displayed inside the banner. |
| `type` | `'default' \| 'success' \| 'error' \| 'warning' \| 'info'` | `'default'` | Sets the message style. |
| `icon` | `ReactNode` | — | Displays an icon beside the message. |
| `alignIcon` | `'left' \| 'right'` | `'right'` | Positions the icon. |
| `onClick` | `(event: MouseEvent) => void` | — | Makes the banner actionable. |
| `to` | `string` | — | Makes the banner navigate to the given URL. |
# Button
Source: https://payloadcms.com/docs/ui-components/button
The `Button` component lets a user perform an action or navigate to another location. Use its style to communicate the importance and consequence of the action.
## Import
Inside a Payload Admin Panel Custom Component:
```tsx
import { Button } from '@payloadcms/ui'
```
You can also import the component directly:
```tsx
import { Button } from '@payloadcms/ui/elements/Button'
```
## Basic usage
Use a primary button for the main action in a section or view.
**Implementation**
```tsx
```
**Styling**
Add this override to your Admin Panel stylesheet to change primary Buttons without changing the rest of the Payload color system.
```css
.btn--style-primary {
--bg-color: #6d5dfc;
--color: #ffffff;
--hover-bg: #5947e5;
--hover-color: #ffffff;
}
```
- `--bg-color`: Button background.
- `--color`: Button label and icon color.
- `--hover-bg`: Background shown on hover, focus, and active states.
- `--hover-color`: Label and icon color shown on hover, focus, and active states.
- `--btn-border`: Optional button border.
- `--hover-btn-border`: Optional border shown on hover, focus, and active states.
## Styles
Use `secondary` for supporting actions. Use `error` when an action is destructive or difficult to reverse.
**Implementation**
```tsx
```
**Styling**
Each Button style has its own class, so a project can customize primary, secondary, and destructive actions independently.
```css
.btn--style-primary {
--bg-color: #6d5dfc;
--color: #ffffff;
--hover-bg: #5947e5;
--hover-color: #ffffff;
}
.btn--style-secondary {
--color: #6d5dfc;
--btn-border: 1px solid #6d5dfc;
--hover-color: #5947e5;
--hover-btn-border: 1px solid #5947e5;
}
.btn--style-error {
--bg-color: #c7362f;
--color: #ffffff;
--hover-bg: #a92c27;
--hover-color: #ffffff;
}
```
- `--bg-color`: Button background.
- `--color`: Button label and icon color.
- `--hover-bg`: Background shown on hover, focus, and active states.
- `--hover-color`: Label and icon color shown on hover, focus, and active states.
- `--btn-border`: Optional button border.
- `--hover-btn-border`: Optional border shown on hover, focus, and active states.
Avoid placing multiple primary buttons next to one another. When actions have equal emphasis, use secondary buttons instead.
## Disabled state
Use `disabled` when an action is temporarily unavailable. When possible, explain what the user must do before the action becomes available.
**Implementation**
```tsx
```
**Styling**
Disabled styles use a more specific selector. Target both classes to customize the unavailable state without changing enabled Buttons.
```css
.btn--style-primary.btn--disabled {
--bg-color: #e3e1f5;
--color: #625d82;
}
```
- `--bg-color`: Disabled button background.
- `--color`: Disabled label and icon color.
## Sizes
Buttons support `xsmall`, `small`, `medium`, and `large` sizes. Use `medium` unless the surrounding interface establishes another size.
**Implementation**
```tsx
```
**Styling**
Target a size class in your Admin Panel stylesheet when a project needs different Button dimensions.
```css
.btn--size-medium {
--btn-padding-block-start: 0.5rem;
--btn-padding-inline-end: 1rem;
--btn-padding-block-end: 0.5rem;
--btn-padding-inline-start: 1rem;
}
```
- `--btn-padding-block-start`: Top padding.
- `--btn-padding-inline-end`: Right padding in left-to-right layouts.
- `--btn-padding-block-end`: Bottom padding.
- `--btn-padding-inline-start`: Left padding in left-to-right layouts.
## Accessibility
- Use a short, action-oriented label that describes what happens next.
- Provide `aria-label` when an icon-only button has no visible label.
- Do not rely on color alone to explain a destructive or disabled action.
- Use `type="submit"` only when the button submits its containing form.
- Use a link-style button for navigation rather than handling navigation in `onClick`.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------ |
| `buttonStyle` | `'primary' \| 'secondary' \| 'error' \| 'dashed' \| 'icon-label' \| 'pill' \| 'subtle' \| 'tab' \| 'transparent' \| 'none'` | `'primary'` | Sets the visual emphasis. |
| `children` | `ReactNode` | — | Visible button content. |
| `disabled` | `boolean` | `false` | Prevents interaction. |
| `icon` | `ReactNode \| 'chevron' \| 'edit' \| 'plus' \| 'x'` | — | Displays an icon beside or instead of the label. |
| `margin` | `boolean` | `true` | Applies the default outer margin. |
| `size` | `'xsmall' \| 'small' \| 'medium' \| 'large'` | `'medium'` | Sets the button height and spacing. |
| `type` | `'button' \| 'submit'` | `'button'` | Sets the native button type. |
# Card
Source: https://payloadcms.com/docs/ui-components/card
The `Card` component groups a title with optional actions. It can also make the entire surface interactive.
## Import
```tsx
import { Card } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
```
**Styling**
Target the Card base class and its interactive modifier to change its surface, border, and hover treatment.
```css
.card {
background: #f3f1ff;
border: 1px solid #c8c0ff;
border-radius: 8px;
}
.card--has-onclick:hover {
background: #ebe7ff;
border-color: #6d5dfc;
}
```
- `background`: Card surface color.
- `border`: Card border width, style, and color.
- `border-radius`: Card corner radius.
## Actions
Use the `actions` prop for controls that act on the card without activating the full card surface.
**Implementation**
```tsx
createPost()}
round
/>
}
title="Posts"
/>
```
**Styling**
Target the Card base class and its interactive modifier to change its surface, border, and hover treatment.
```css
.card {
background: #f3f1ff;
border: 1px solid #c8c0ff;
border-radius: 8px;
}
.card--has-onclick:hover {
background: #ebe7ff;
border-color: #6d5dfc;
}
```
- `background`: Card surface color.
- `border`: Card border width, style, and color.
- `border-radius`: Card corner radius.
When using `href` or `onClick`, provide `buttonAriaLabel` so the full-card control has an accessible name.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ----------------- | ------------- | ------- | ----------------------------------------------- |
| `title` \* | `string` | — | Visible card title. |
| `titleAs` | `ElementType` | `'div'` | Sets the element used for the title. |
| `actions` | `ReactNode` | — | Displays controls on the trailing edge. |
| `href` | `string` | — | Makes the full card navigate to the given path. |
| `onClick` | `() => void` | — | Makes the full card perform an action. |
| `buttonAriaLabel` | `string` | — | Labels the full-card interactive control. |
_\* An asterisk denotes that a prop is required._
# CodeEditor
Source: https://payloadcms.com/docs/ui-components/code-editor
The `CodeEditor` component wraps Monaco Editor with Payload's styling and automatic height behavior.
## Import
`CodeEditor` loads Monaco eagerly. Use `CodeEditorLazy` to defer loading the editor until it renders, which keeps Monaco out of the initial bundle of a Client Component.
```tsx
import { CodeEditor, CodeEditorLazy } from '@payloadcms/ui'
```
## Read-only usage
**Implementation**
```tsx
```
**Styling**
Style the CodeEditor frame directly. Monaco exposes its own variables for editor-specific colors.
```css
.code-editor {
border: 1px solid #c8c0ff;
border-radius: 8px;
overflow: hidden;
}
.code-editor .monaco-editor {
--vscode-editor-background: #151225;
}
```
- `--vscode-editor-background`: Monaco editor surface.
- `border`: Outer editor border.
- `border-radius`: Outer editor corner radius.
Set `defaultLanguage` to enable the appropriate syntax highlighting. Use `readOnly` when displaying code that should not be edited.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ---------------------- | -------------------------- | ------- | ----------------------------------------------- |
| `value` | `string` | — | Code displayed by the editor. |
| `defaultLanguage` | `string` | — | Sets the Monaco language used for highlighting. |
| `onChange` | `(value?: string) => void` | — | Runs when editable content changes. |
| `readOnly` | `boolean` | `false` | Prevents editing. |
| `minHeight` | `number` | `56` | Sets the minimum editor height in pixels. |
| `maxHeight` | `number` | — | Caps the automatically calculated height. |
| `options` | `MonacoEditorOptions` | — | Overrides supported Monaco Editor options. |
| `recalculatedHeightAt` | `number` | — | Triggers height recalculation when increased. |
# Collapsible
Source: https://payloadcms.com/docs/ui-components/collapsible
The `Collapsible` component groups content beneath a header that can be expanded or collapsed. It manages its own state by default and can also be controlled by a parent component.
## Import
```tsx
import { Collapsible } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
Collapsible content
```
**Styling**
Customize the Collapsible frame, header, and content surfaces independently with its structural classes.
```css
.collapsible--style-default {
border-color: #c8c0ff;
}
.collapsible--style-default > .collapsible__toggle-wrap {
background: #f3f1ff;
}
.collapsible__content {
background: #ffffff;
}
```
- `background`: Header or content surface.
- `border-color`: Collapsed section outline.
Use a concise header that describes the hidden content. The entire header is used as the toggle target unless `disableHeaderToggle` is enabled.
## Error state
**Implementation**
```tsx
Correct the invalid fields in this section.
```
**Styling**
Target the error modifier when a customer’s validation palette differs from Payload’s defaults.
```css
.collapsible--style-error {
border-color: #dc2626;
}
.collapsible--style-error > .collapsible__toggle-wrap {
background: #fee2e2;
}
.collapsible--style-error .row-label {
color: #7f1d1d;
}
```
- `background`: Error header surface.
- `border-color`: Error outline.
- `color`: Error header label.
Use the `error` style when the section contains invalid fields that require attention.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ------------------------ | ------------------------------ | ----------- | ------------------------------------------------------ |
| `children` \* | `ReactNode` | — | Content displayed inside the collapsible section. |
| `header` | `ReactNode` | — | Content displayed in the section header. |
| `actions` | `ReactNode` | — | Displays controls on the trailing edge of the header. |
| `collapsibleStyle` | `'default' \| 'error'` | `'default'` | Sets the visual state. |
| `initCollapsed` | `boolean` | `false` | Sets the initial uncontrolled state. |
| `isCollapsed` | `boolean` | — | Controls the collapsed state from a parent component. |
| `onToggle` | `(collapsed: boolean) => void` | — | Runs when the section is toggled. |
| `disableHeaderToggle` | `boolean` | `false` | Prevents the header from toggling the section. |
| `disableToggleIndicator` | `boolean` | `false` | Hides the trailing chevron. |
| `AfterCollapsible` | `ReactNode` | — | Renders content after the collapsible content wrapper. |
_\* An asterisk denotes that a prop is required._
# CopyToClipboard
Source: https://payloadcms.com/docs/ui-components/copy-to-clipboard
The `CopyToClipboard` component renders a compact copy control with tooltip feedback. Use it beside values such as IDs, URLs, and API keys.
## Import
```tsx
import { CopyToClipboard } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
post_123456789
```
**Styling**
Style the copy control itself while leaving its tooltip and clipboard behavior unchanged.
```css
.copy-to-clipboard {
background: #f3f1ff;
color: #5947e5;
border-radius: 6px;
padding: 0.25rem;
}
.copy-to-clipboard:hover {
background: #ded8ff;
}
```
- `background`: Copy control surface.
- `color`: Icon color inherited by the control.
- `padding`: Clickable area around the icon.
The control is not rendered when `value` is empty. Provide surrounding text so users understand what will be copied.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ---------------- | -------- | ---------------------- | ----------------------------------------- |
| `value` | `string` | — | Value written to the clipboard. |
| `defaultMessage` | `string` | Localized copy label | Tooltip shown before the value is copied. |
| `successMessage` | `string` | Localized copied label | Tooltip shown after a successful copy. |
# DatePicker
Source: https://payloadcms.com/docs/ui-components/date-picker
The `DatePicker` component wraps `react-datepicker` with Payload's styles, localization, and date and time display options.
## Import
```tsx
import { DatePicker } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
const [value, setValue] = useState()
setValue(date || undefined)}
placeholder="Select a date"
value={value}
/>
```
**Styling**
Customize the input and calendar separately using the DatePicker wrapper and react-datepicker state classes.
```css
.date-time-picker .react-datepicker__input-container input {
background: #ffffff;
border: 1px solid #6d5dfc;
border-radius: 8px;
}
.date-time-picker .react-datepicker {
background: #ffffff;
border-color: #c8c0ff;
}
.date-time-picker .react-datepicker__day--selected {
background: #6d5dfc;
color: #ffffff;
}
```
- `background`: Input, calendar, or selected-day surface.
- `border`: Input and calendar outline.
- `color`: Selected-day text color.
Pair the picker with a visible label. `id` is applied to the picker's wrapper element, not to the underlying input, so pass the input's ID through `overrides` and point the label's `htmlFor` at that value instead:
```tsx
```
The picker calls `onChange` with `null` when its clear button is used.
## Picker appearances
Use `pickerAppearance` to choose between a date, date and time, day, month, or time input. Payload selects a matching display format when `displayFormat` is not provided.
| Appearance | Input |
| ------------ | ---------------------- |
| `default` | Calendar date |
| `dayAndTime` | Calendar date and time |
| `dayOnly` | Day and month |
| `monthOnly` | Month |
| `timeOnly` | Time |
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ------------------ | --------------------------------------------------------------------- | ----------- | ------------------------------------------------ |
| `id` | `string` | — | Sets the ID of the picker's wrapper element. |
| `value` | `Date \| string` | — | Selected date or date string. |
| `onChange` | `(value: Date \| null) => void` | — | Runs when the selected value changes. |
| `placeholder` | `string` | — | Displays text when no value is selected. |
| `pickerAppearance` | `'default' \| 'dayAndTime' \| 'dayOnly' \| 'monthOnly' \| 'timeOnly'` | `'default'` | Selects the date and time controls shown. |
| `displayFormat` | `string` | Automatic | Overrides the date-fns display format. |
| `minDate` | `Date` | — | Sets the earliest selectable date. |
| `maxDate` | `Date` | — | Sets the latest selectable date. |
| `monthsToShow` | `1 \| 2` | `1` | Sets the number of visible calendar months. |
| `timeIntervals` | `number` | `30` | Sets the number of minutes between time options. |
| `readOnly` | `boolean` | `false` | Prevents the value from being changed. |
| `overrides` | `ReactDatePickerProps` | — | Passes supported options to `react-datepicker`. |
# Document Actions
Source: https://payloadcms.com/docs/ui-components/document-actions
Payload's document action components connect standard Admin controls to the current form, document, permissions, and selection state. Use them inside the corresponding Edit or List View rather than as general-purpose buttons.
## Import
```tsx
import {
DeleteMany,
EditMany,
PublishButton,
PublishMany,
PreviewButton,
SaveButton,
SaveDraftButton,
UnpublishButton,
UnpublishMany,
} from '@payloadcms/ui'
```
## Save
When drafts are disabled, `SaveButton` submits the current document form. It is disabled while an upload is in progress and, when editing an existing document, until the form has been modified.
**Implementation**
```tsx
```
**Styling**
Use the Save action ID together with its Button style class when the document Save control needs treatment distinct from other primary Admin buttons.
```css
#action-save.btn--style-primary {
--bg-color: #6d5dfc;
--color: #ffffff;
--hover-bg: #5947e5;
--hover-color: #ffffff;
}
```
- `--bg-color`: Save action background.
- `--color`: Save action label color.
- `--hover-bg`: Save action background on interaction.
- `--hover-color`: Save action label color on interaction.
Pass `label` to `SaveButton` when a replacement component needs a more specific action label.
## Draft and publish actions
When drafts are enabled, Payload uses `SaveDraftButton` and `PublishButton` instead of `SaveButton`. Saving a draft skips validation; publishing submits the document with a published status.
**Implementation**
```tsx
```
**Styling**
Use the stable action IDs together with Button style classes when Save and Publish controls need treatment distinct from other Admin buttons.
```css
#action-save-draft.btn--style-secondary {
--color: #6d5dfc;
--btn-border: 1px solid #6d5dfc;
--hover-color: #5947e5;
--hover-btn-border: 1px solid #5947e5;
}
#action-publish.btn--style-primary {
--bg-color: #6d5dfc;
--color: #ffffff;
--hover-bg: #5947e5;
--hover-color: #ffffff;
}
```
- `--bg-color`: Primary action background.
- `--color`: Action label color.
- `--hover-bg`: Primary action background on interaction.
- `--btn-border`: Secondary action border.
- `--hover-btn-border`: Secondary action border on interaction.
`UnpublishButton` appears in the document action menu when the current user can publish, the document is published, and drafts are enabled. It opens a confirmation modal before changing the document back to draft status.
`PreviewButton` opens the current document's configured preview URL in a new tab. It only renders when the Edit View's live preview context contains a URL.
Use these components when replacing the equivalent Edit View control. The surrounding Edit View supplies their form, document information, operation, locale, translation, and modal contexts.
```ts
export const Posts = {
slug: 'posts',
admin: {
components: {
edit: {
SaveDraftButton: '/components/CustomSaveDraftButton',
PublishButton: '/components/CustomPublishButton',
UnpublishButton: '/components/CustomUnpublishButton',
},
},
},
}
```
See [Edit View Custom Components](/docs/v3/custom-components/edit-view.md) for all Collection and Global replacement slots and their server and client prop types.
## Bulk List View actions
The bulk action components operate on the documents selected in the current List View.
**Implementation**
```tsx
```
**Styling**
Bulk actions share the ListSelection button treatment. Override that class to customize selection actions without changing standard Buttons.
```css
.list-selection__button {
color: #6d5dfc;
font-weight: 600;
}
.list-selection__button:hover,
.list-selection__button:focus-visible {
color: #5947e5;
text-decoration: underline;
}
```
- `color`: Bulk action label color.
- `font-weight`: Bulk action label emphasis.
- `text-decoration`: Interaction feedback for bulk actions.
Each component receives the current Collection's client configuration. Selection and authentication providers determine whether the action is visible and which documents it affects. `PublishMany` and `UnpublishMany` only render for Collections with drafts enabled.
These controls are intended for List View composition. They do not create selection state when rendered elsewhere.
## Behavior and visibility
| Component | Context | When it is used |
| ----------------- | --------- | -------------------------------------------------------------- |
| `SaveButton` | Edit View | Saves a document when drafts are disabled. |
| `SaveDraftButton` | Edit View | Saves a draft without requiring the form to pass validation. |
| `PublishButton` | Edit View | Publishes the current document when drafts are enabled. |
| `UnpublishButton` | Edit View | Returns a published document to draft status after confirming. |
| `PreviewButton` | Edit View | Opens the configured document preview in a new tab. |
| `EditMany` | List View | Opens the bulk-edit workflow for selected documents. |
| `PublishMany` | List View | Publishes selected documents. |
| `UnpublishMany` | List View | Unpublishes selected documents. |
| `DeleteMany` | List View | Trashes or permanently deletes selected documents. |
## Accessibility
- Keep replacement labels action-oriented and specific to the operation.
- Preserve the built-in confirmation step for destructive or difficult-to-reverse actions.
- Do not force an action to remain visible when Payload hides it because of permissions or document state.
- Keep custom action order consistent with the surrounding Edit or List View.
## Provider requirements
These components depend on providers supplied by the Payload Admin Panel. Edit actions require the current form and document contexts. Bulk actions require the current Collection, selection, authentication, and List View contexts. Use [`Button`](/docs/v3/ui-components/button.md) for actions that do not need this behavior.
# Document State
Source: https://payloadcms.com/docs/ui-components/document-state
Payload's document state components communicate editing conflicts and protect users from losing work. The Admin Panel renders these states automatically during its standard document workflow; use the exports when composing or replacing an Edit View experience.
## Import
```tsx
import {
DocumentLocked,
DocumentTakeOver,
LeaveWithoutSaving,
Locked,
} from '@payloadcms/ui'
```
## Lock indicator
`Locked` displays the compact lock icon used when another user is editing a document. Its tooltip identifies that user when their email or ID is available.
**Implementation**
```tsx
```
**Styling**
Style the compact Locked indicator directly while preserving its built-in editor tooltip.
```css
.locked {
color: #6d5dfc;
background: #f3f1ff;
border: 1px solid #c8c0ff;
border-radius: 999px;
padding: 0.35rem;
}
```
- `color`: Lock icon color.
- `background`: Indicator surface.
- `border`: Indicator outline.
- `padding`: Clickable area around the lock icon.
## Locked document
`DocumentLocked` interrupts editing and gives the user three appropriate paths: return to the previous view, inspect the document without editing, or take over editing.
**Implementation**
```tsx
router.back()}
isActive={isLocked}
onReadOnly={() => setReadOnly(true)}
onTakeOver={takeOverDocument}
updatedAt={lockedAt}
user={lockingUser}
/>
```
**Styling**
Target each document-state surface and its inner wrapper to customize the interruption without changing its permission or navigation behavior.
```css
.document-locked,
.document-take-over {
background: rgb(255 255 255 / 88%);
backdrop-filter: blur(8px);
}
.document-locked__wrapper,
.document-take-over__wrapper {
border: 1px solid #c8c0ff;
border-radius: 8px;
}
```
- `background`: Surface displayed over the interrupted Edit View.
- `backdrop-filter`: Visual separation from the underlying document.
- `border`: Optional outline around the state message.
- `border-radius`: State message corner radius.
The component opens while `isActive` is true. Supply callbacks for each navigation or takeover outcome; the component coordinates its own modal visibility and clears Payload's route cache when required.
## Editing taken over
`DocumentTakeOver` is shown to the previous editor after another user takes control of the document. It lets them return to the dashboard or continue in read-only mode.
**Implementation**
```tsx
router.push('/admin')}
isActive={hasLostEditAccess}
onReadOnly={() => setReadOnly(true)}
/>
```
**Styling**
Target each document-state surface and its inner wrapper to customize the interruption without changing its permission or navigation behavior.
```css
.document-locked,
.document-take-over {
background: rgb(255 255 255 / 88%);
backdrop-filter: blur(8px);
}
.document-locked__wrapper,
.document-take-over__wrapper {
border: 1px solid #c8c0ff;
border-radius: 8px;
}
```
- `background`: Surface displayed over the interrupted Edit View.
- `backdrop-filter`: Visual separation from the underlying document.
- `border`: Optional outline around the state message.
- `border-radius`: State message corner radius.
## Unsaved changes
`LeaveWithoutSaving` watches the current document form and opens a confirmation modal when an authenticated user attempts to navigate away from modified or invalid content.
```tsx
{
analytics.track('Left document with unsaved changes')
}}
/>
```
Set `disablePreventLeave` when another part of a custom workflow temporarily owns navigation. Use `onPrevent` to observe the attempted destination and `onConfirm` for work that must happen after the user accepts leaving.
## Common props
These are the props most commonly used with this component family. See the exported types in `@payloadcms/ui` for the complete list.
### Locked
| Prop | Type | Default | Description |
| ----------- | ------------ | ------- | ------------------------------------------- |
| `user` \* | `ClientUser` | — | Identifies the user currently editing. |
| `className` | `string` | — | Adds a class to the lock indicator wrapper. |
### DocumentLocked
| Prop | Type | Default | Description |
| ----------------- | -------------------------------- | ------- | ----------------------------------------------- |
| `isActive` \* | `boolean` | — | Opens or closes the locked-document modal. |
| `handleGoBack` \* | `() => void` | — | Returns the user to the previous safe location. |
| `onReadOnly` \* | `() => void` | — | Switches the document into read-only mode. |
| `onTakeOver` \* | `() => void` | — | Takes control of editing the document. |
| `updatedAt` | `number \| null` | — | Displays the locking editor's last activity. |
| `user` | `ClientUser \| string \| number` | — | Identifies the locking user. |
### DocumentTakeOver
| Prop | Type | Default | Description |
| -------------------------- | ------------ | ------- | ---------------------------------------------- |
| `isActive` \* | `boolean` | — | Opens or closes the takeover modal. |
| `handleBackToDashboard` \* | `() => void` | — | Returns the previous editor to the dashboard. |
| `onReadOnly` \* | `() => void` | — | Continues with the document in read-only mode. |
_\* An asterisk denotes that a prop is required._
## Accessibility
- Preserve the built-in modal focus management when composing conflict states.
- Identify the other editor when that information can be shown safely.
- Keep read-only and takeover actions explicit; they have meaning beyond ordinary navigation.
- Do not rely on the lock icon alone when the editing conflict needs an explanation.
## Provider requirements
These components depend on the document, form, authentication, translation, modal, route-cache, and route-transition contexts supplied by the Admin Panel. They are intended for Custom Components rendered within Payload's document workflow, not standalone application status messages.
# Draggable Sortable
Source: https://payloadcms.com/docs/ui-components/draggable-sortable
Use `DraggableSortable` with `DraggableSortableItem` to add pointer, touch, and keyboard reordering to custom Admin Panel interfaces.
## Import
```tsx
import { DraggableSortable, DraggableSortableItem } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
'use client'
import {
Button,
DraggableSortable,
DraggableSortableItem,
DragHandleIcon,
} from '@payloadcms/ui'
import { useState } from 'react'
const initialItems = [
{ id: 'overview', label: 'Overview' },
{ id: 'details', label: 'Details' },
{ id: 'settings', label: 'Settings' },
]
const moveItem = (items, moveFromIndex, moveToIndex) => {
const nextItems = [...items]
const [movedItem] = nextItems.splice(moveFromIndex, 1)
nextItems.splice(moveToIndex, 0, movedItem)
return nextItems
}
export function SortableList() {
const [items, setItems] = useState(initialItems)
return (
id)}
onDragEnd={({ moveFromIndex, moveToIndex }) => {
setItems((current) => moveItem(current, moveFromIndex, moveToIndex))
}}
>
{items.map(({ id, label }) => (
{({ attributes, isDragging, listeners, setNodeRef, transform, transition }) => (
{label}
}
margin={false}
/>
)}
))}
)
}
```
**Styling**
Style the list and row wrappers while leaving the drag transform and transition inline on each item. Payload theme variables keep the surface consistent across light and dark modes.
```css
.sortable-list {
display: grid;
gap: calc(var(--base) / 2);
}
.sortable-item {
align-items: center;
background: var(--theme-elevation-50);
border: 1px solid var(--theme-elevation-200);
display: flex;
justify-content: space-between;
padding: calc(var(--base) / 2) var(--base);
}
```
- `--theme-elevation-50`: Sortable item background.
- `--theme-elevation-200`: Sortable item border.
- `--base`: Spacing within and between items.
`DraggableSortable` coordinates the list and reports the original and destination indexes through `onDragEnd`. Your component remains responsible for updating local state and persisting the new order when necessary.
Each `DraggableSortableItem` exposes the attributes, listeners, ref, transform, and transition needed to render an item. Apply the listeners to a visible drag handle so the rest of the row can retain its own interactions.
## DraggableSortable props
| Prop | Type | Description |
| ------------- | ------------------------------------------------- | ---------------------------------------------- |
| `children*` | `ReactNode` | Renders the sortable items. |
| `ids*` | `string[]` | Lists item IDs in their current display order. |
| `onDragEnd*` | `({ moveFromIndex, moveToIndex, event }) => void` | Handles a completed reorder. |
| `onDragStart` | `({ id, event }) => void` | Handles the beginning of a drag. |
| `className` | `string` | Adds a class to the sortable list container. |
## DraggableSortableItem props
| Prop | Type | Description |
| ----------- | ------------------------------ | ----------------------------------------------------------- |
| `id*` | `string` | Matches an entry in the parent `ids` array. |
| `children*` | `(sortableState) => ReactNode` | Renders the item with its drag attributes and visual state. |
| `disabled` | `boolean` | Prevents the item from being reordered. |
_\* An asterisk denotes that a prop is required._
## Accessibility
- Put the supplied attributes and listeners on a keyboard-focusable drag handle.
- Give the handle an accessible name that identifies the item it moves.
- Persist the resulting order only after `onDragEnd` supplies a valid destination.
These components configure their own drag-and-drop context and require no Payload providers.
# Dropzone
Source: https://payloadcms.com/docs/ui-components/dropzone
The `Dropzone` component adds drag, drop, and paste handling to a container. It passes the selected `FileList` to `onChange` and applies Payload's drop-target styles.
## Import
```tsx
import { Dropzone } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx
const inputRef = useRef(null)
const [fileNames, setFileNames] = useState([])
const selectFiles = (files: FileList) => {
setFileNames(Array.from(files, (file) => file.name))
}
event.target.files && selectFiles(event.target.files)}
ref={inputRef}
type="file"
/>
{fileNames.length ? fileNames.join(', ') : 'or drag and drop files here'}
```
**Styling**
Target the base Dropzone and its dragging state to create a branded upload area and clear drag feedback.
```css
.dropzone {
background: #faf9ff;
border: 2px dashed #8b7cf6;
border-radius: 10px;
}
.dropzone.dragging {
background: #ede9fe;
border-color: #6d5dfc;
}
```
- `background`: Idle or dragging surface.
- `border`: Drop target outline.
- `border-radius`: Dropzone corner radius.
`Dropzone` does not render a file input or open the system file picker. Render those controls as children when users should be able to browse for files in addition to dropping them.
It also has no awareness of Collections or existing documents, so controls such as **Create New** and **Choose from existing** must come from the surrounding field interface.
When `multipleFiles` is disabled, dropping more than one file passes only the first file to `onChange`.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| --------------- | --------------------------- | ----------- | -------------------------------------------------- |
| `children` | `ReactNode` | — | Content and file-selection controls in the target. |
| `onChange` \* | `(files: FileList) => void` | — | Runs when files are dropped, pasted, or supplied. |
| `multipleFiles` | `boolean` | `false` | Allows more than one file to be returned. |
| `disabled` | `boolean` | `false` | Disables drag, drop, and paste listeners. |
| `dropzoneStyle` | `'default' \| 'none'` | `'default'` | Enables or removes Payload's drop-target styles. |
| `className` | `string` | — | Adds a class to the dropzone container. |
_\* An asterisk denotes that a prop is required._
# Field Labels and Messages
Source: https://payloadcms.com/docs/ui-components/field-labels-and-messages
`FieldLabel`, `FieldDescription`, and `FieldError` provide the supporting text and validation treatment used around inputs in the Payload Admin Panel. Use them when a Custom Field needs to match Payload's field structure without rendering one of the complete field components.
## Import
```tsx
import { FieldDescription, FieldError, FieldLabel } from '@payloadcms/ui'
```
## Labels
**Implementation**
```tsx
```
**Styling**
Style the shared FieldLabel class and its required marker to keep custom inputs aligned with the rest of the Admin Panel.
```css
.field-label {
color: var(--theme-text);
font-weight: 600;
}
.field-label .required {
color: var(--theme-error-500);
}
```
- `color`: Label and required-marker color.
- `font-weight`: Label emphasis.
This example places `FieldLabel` above a native input to show how it fits into a complete Custom Field. Connect the label to its input with `htmlFor`. When `htmlFor` is omitted, `FieldLabel` can derive the matching field ID from `path` inside Payload's form context.
## Descriptions and errors
**Implementation**
```tsx
```
**Styling**
Customize helper and validation messages through their shared classes while preserving the visibility behavior controlled by the field state.
```css
.field-description {
color: var(--theme-elevation-600);
font-size: 0.875rem;
}
.field-error.tooltip {
background: var(--theme-error-500);
color: var(--theme-elevation-0);
}
```
- `background`: Validation tooltip surface.
- `color`: Helper or validation text color.
- `font-size`: Helper text size.
`FieldDescription` renders helper text below the control, while `FieldError` positions validation feedback against the same field. `FieldError` can receive `message` and `showError` directly, or read the current message and submitted state from Payload's form context.
## Common props
### FieldLabel
| Prop | Type | Default | Description |
| ------------ | --------------------------- | --------- | ------------------------------------------------- |
| `label` | `StaticLabel` | — | Supplies the translated visible label. |
| `htmlFor` | `string` | Generated | Associates the label with an input. |
| `path` | `string` | — | Generates the input ID when `htmlFor` is omitted. |
| `required` | `boolean` | `false` | Displays Payload's required marker. |
| `localized` | `boolean` | `false` | Displays the active locale beside the label. |
| `hideLocale` | `boolean` | `false` | Hides the active locale label. |
| `as` | `'h3' \| 'label' \| 'span'` | `'label'` | Changes the rendered element. |
| `unstyled` | `boolean` | `false` | Removes Payload's label presentation. |
### FieldDescription
| Prop | Type | Default | Description |
| ----------------- | ------------------- | ------- | --------------------------------------------- |
| `description` | `StaticDescription` | — | Supplies translated helper content. |
| `path` \* | `string` | — | Adds the field-specific description class. |
| `className` | `string` | — | Adds a custom class. |
| `marginPlacement` | `'bottom' \| 'top'` | — | Applies Payload's standard description space. |
### FieldError
| Prop | Type | Default | Description |
| ------------ | ------------------------------- | --------- | ------------------------------------------ |
| `path` | `string` | — | Reads validation state for this form path. |
| `message` | `string` | Form | Overrides the message from form state. |
| `showError` | `boolean` | Form | Forces the validation message to render. |
| `alignCaret` | `'center' \| 'left' \| 'right'` | `'right'` | Positions the validation tooltip caret. |
_\* An asterisk denotes that a prop is required._
## Accessibility
- Associate visible labels with their inputs.
- Use descriptions for guidance, not essential validation errors.
- Keep validation messages specific and explain how to correct the value.
- Preserve `aria-describedby` and error associations when composing a complete custom input.
## Provider requirements
These components use Payload's translation context. `FieldLabel` can also read form, locale, and edit-depth context, while `FieldError` reads form validation state when explicit values are not supplied. Those providers are already present inside Admin Panel Custom Components.
# Text Inputs
Source: https://payloadcms.com/docs/ui-components/text-inputs
`TextInput` and `TextareaInput` combine native text controls with Payload's label, description, validation, and read-only field presentation. Use them when building a Custom Field that manages its own value.
## Import
```tsx
import { TextareaInput, TextInput } from '@payloadcms/ui'
```
## TextInput
**Implementation**
```tsx
const [value, setValue] = useState('Quarterly report')
setValue(event.target.value)}
path="title"
required
value={value}
/>
```
**Styling**
Target the TextInput field wrapper and native input to customize its surface, outline, and focus treatment.
```css
.field-type.text input {
background: var(--theme-input-bg);
border: 1px solid var(--theme-elevation-250);
border-radius: 6px;
}
.field-type.text input:focus {
border-color: var(--theme-elevation-500);
}
```
- `--theme-input-bg`: Input surface inherited from the theme.
- `border`: Input outline.
- `border-radius`: Input corner radius.
`TextInput` is controlled. Pass the current string through `value` and update it from the native change event.
Set `hasMany` to render Payload's sortable multi-value text control instead of a native input.
## TextareaInput
**Implementation**
```tsx
const [value, setValue] = useState(
'A concise summary for the project dashboard.',
)
setValue(event.target.value)}
path="summary"
rows={4}
value={value}
/>
```
**Styling**
Style the native textarea inside Payload’s field wrapper while retaining its label, description, and validation layout.
```css
.field-type.textarea textarea {
background: var(--theme-input-bg);
border: 1px solid var(--theme-elevation-250);
border-radius: 6px;
resize: vertical;
}
.field-type.textarea textarea:focus {
border-color: var(--theme-elevation-500);
}
```
- `--theme-input-bg`: Textarea surface inherited from the theme.
- `border`: Textarea outline.
- `border-radius`: Textarea corner radius.
- `resize`: Allowed manual resize direction.
Use `rows` to establish the initial height. The value remains controlled by the surrounding Custom Field.
## Common props
These props are shared by both inputs unless noted otherwise.
| Prop | Type | Default | Description |
| ------------- | ------------------------- | ------- | ---------------------------------------------- |
| `path` \* | `string` | — | Identifies the field and connects its UI. |
| `value` | `string` | `''` | Supplies the controlled value. |
| `onChange` | `ChangeEventHandler` | — | Receives native input changes. |
| `label` | `StaticLabel` | — | Displays Payload's field label. |
| `description` | `StaticDescription` | — | Displays helper text beneath the input. |
| `placeholder` | `string \| LabelFunction` | — | Displays translated placeholder text. |
| `required` | `boolean` | `false` | Displays the required marker. |
| `readOnly` | `boolean` | `false` | Disables editing and applies read-only styles. |
| `showError` | `boolean` | Form | Displays the current validation error. |
| `BeforeInput` | `ReactNode` | — | Inserts content before the native control. |
| `AfterInput` | `ReactNode` | — | Inserts content after the native control. |
| `rows` | `number` | — | Sets the TextareaInput row count. |
| `hasMany` | `boolean` | `false` | Enables TextInput's multi-value mode. |
_\* An asterisk denotes that a prop is required._
## Custom field usage
Inside a Custom Field, connect the input to Payload's form state with `useField`:
```tsx
'use client'
import { TextInput, useField } from '@payloadcms/ui'
export function CustomTextField({ field, path }) {
const { setValue, showError, value } = useField({ path })
return (
setValue(event.target.value)}
path={path}
required={field.required}
showError={showError}
value={value}
/>
)
}
```
## Provider requirements
The Admin Panel supplies the translation and form contexts used by the built-in label, description, and validation components. Use these inputs inside a Payload Custom Field, or provide equivalent contexts when rendering them elsewhere.
# Checkbox and Select Inputs
Source: https://payloadcms.com/docs/ui-components/checkbox-and-select-inputs
`CheckboxInput` and `SelectInput` provide the input-level controls used by Payload's Checkbox and Select fields. They are useful when a Custom Field needs standard Admin styling while controlling its own state.
## Import
```tsx
import { CheckboxInput, SelectInput } from '@payloadcms/ui'
```
## CheckboxInput
**Implementation**
```tsx
const [checked, setChecked] = useState(true)
setChecked(event.target.checked)}
/>
```
**Styling**
Style the checkbox control and its checked modifier while leaving the native input and label connection intact.
```css
.checkbox-input__input {
border-color: var(--theme-elevation-400);
border-radius: 4px;
}
.checkbox-input--checked .checkbox-input__input {
background: var(--theme-elevation-800);
color: var(--theme-elevation-0);
}
```
- `background`: Checked control surface.
- `border-color`: Checkbox outline.
- `border-radius`: Checkbox corner radius.
- `color`: Check icon color.
Use `checked` and `onToggle` to control the value. Set `partialChecked` to display the indeterminate treatment used by list-selection interfaces.
## SelectInput
**Implementation**
```tsx
const options = [
{ label: 'Planned', value: 'planned' },
{ label: 'In progress', value: 'in-progress' },
{ label: 'Complete', value: 'complete' },
]
const [value, setValue] = useState('in-progress')
setValue(option?.value || '')}
options={options}
path="status"
value={value}
/>
```
**Styling**
Customize the ReactSelect control within SelectInput while retaining Payload’s field label, description, and error structure.
```css
.field-type.select .rs__control {
background: var(--theme-input-bg);
border-color: var(--theme-elevation-250);
border-radius: 6px;
}
.field-type.select .rs__control--is-focused {
border-color: var(--theme-elevation-500);
}
```
- `--theme-input-bg`: Select control surface.
- `border-color`: Resting and focused outline color.
- `border-radius`: Select control corner radius.
`SelectInput` wraps [`ReactSelect`](/docs/v3/ui-components/react-select.md) with Payload's field label, description, and validation structure. Its controlled `value` contains the stored option value rather than the complete option object.
## CheckboxInput props
| Prop | Type | Default | Description |
| ---------------- | -------------------------------------- | ------- | ------------------------------------------ |
| `checked` | `boolean` | `false` | Controls the checked state. |
| `onToggle` \* | `ChangeEventHandler` | — | Receives changes from the native checkbox. |
| `label` | `StaticLabel` | — | Displays the checkbox label. |
| `partialChecked` | `boolean` | `false` | Displays the indeterminate treatment. |
| `readOnly` | `boolean` | `false` | Disables interaction. |
| `required` | `boolean` | `false` | Marks the native input as required. |
## SelectInput props
| Prop | Type | Default | Description |
| ------------- | ---------------------- | ------- | --------------------------------------------- |
| `name` \* | `string` | — | Names the select input. |
| `path` \* | `string` | — | Identifies the field and connects its UI. |
| `options` \* | `OptionObject[]` | — | Supplies the available translated options. |
| `value` | `string \| string[]` | — | Supplies the selected stored value or values. |
| `onChange` | `ReactSelect.onChange` | — | Receives the selected option object. |
| `hasMany` | `boolean` | `false` | Enables multiple values. |
| `isSortable` | `boolean` | `true` | Enables reordering in multi-value mode. |
| `isClearable` | `boolean` | `true` | Allows the selected value to be cleared. |
| `readOnly` | `boolean` | `false` | Disables interaction. |
| `showError` | `boolean` | Form | Displays the current validation error. |
_\* An asterisk denotes that a prop is required._
## Provider requirements
Both inputs use Payload's translation context. `SelectInput` also composes the standard field label, description, and error components, which can read the surrounding form context in a Custom Field.
# Relationship and Upload Inputs
Source: https://payloadcms.com/docs/ui-components/relationship-and-upload-inputs
`RelationshipInput` and `UploadInput` are the input-level interfaces used by Payload's relationship-backed fields. Use them when replacing a field component while preserving Payload's document-selection workflows.
## Import
```tsx
import { RelationshipInput, UploadInput } from '@payloadcms/ui'
```
## RelationshipInput
**Implementation**
```tsx
const [value, setValue] = useState([])
```
**Styling**
Style the RelationshipInput select surface through its field wrapper while preserving document drawer behavior.
```css
.field-type.relationship .rs__control {
background: var(--theme-input-bg);
border-color: var(--theme-elevation-250);
border-radius: 6px;
}
```
- `--theme-input-bg`: Relationship control surface.
- `border-color`: Relationship control outline.
- `border-radius`: Relationship control corner radius.
`RelationshipInput` selects documents from one or more Collections. Use `appearance="select"` for searchable inline results or `appearance="drawer"` to choose documents from a List Drawer.
## UploadInput
**Implementation**
```tsx
const [value, setValue] = useState()
```
**Styling**
Customize the UploadInput drop target and its Create New and Choose from existing controls without changing their drawer behavior.
```css
.field-type.upload .dropzone {
background: var(--theme-elevation-50);
border-color: var(--theme-elevation-250);
border-radius: 6px;
}
.field-type.upload .btn--style-pill {
border-color: var(--theme-elevation-300);
}
```
- `background`: Upload selection surface.
- `border-color`: Drop target or action outline.
- `border-radius`: Upload selection corner radius.
`UploadInput` relates documents from an upload-enabled Collection. Its empty state includes **Create New**, **Choose from existing**, and drag-and-drop file intake when the current user has permission to create uploads.
This differs from [`Upload`](/docs/v3/ui-components/upload.md), which manages the file attached to an upload-enabled Collection's own Edit View, and [`Dropzone`](/docs/v3/ui-components/dropzone.md), which only handles incoming files.
## RelationshipInput props
| Prop | Type | Default | Description |
| --------------- | ------------------------------------------ | ---------- | ----------------------------------------------- |
| `relationTo` \* | `string[]` | — | Identifies the selectable Collections. |
| `path` \* | `string` | — | Identifies the form field. |
| `hasMany` \* | `boolean` | — | Selects one document or multiple documents. |
| `value` | `ValueWithRelation \| ValueWithRelation[]` | — | Supplies the controlled relationship value. |
| `onChange` \* | `(value) => void` | — | Receives the updated relationship value. |
| `appearance` | `'select' \| 'drawer'` | `'select'` | Chooses inline search or List Drawer selection. |
| `allowCreate` | `boolean` | `true` | Allows creating related documents. |
| `allowEdit` | `boolean` | `true` | Allows opening selected documents for editing. |
| `filterOptions` | `FilterOptionsResult` | — | Restricts selectable documents. |
| `isSortable` | `boolean` | `true` | Enables reordering when `hasMany` is enabled. |
| `readOnly` | `boolean` | `false` | Disables selection and editing. |
## UploadInput props
| Prop | Type | Default | Description |
| ---------------- | ----------------------------------------- | ------- | ----------------------------------------------- |
| `relationTo` \* | `string \| string[]` | — | Identifies the upload-enabled Collections. |
| `path` \* | `string` | — | Identifies the form field. |
| `value` | `string \| number \| ValueWithRelation[]` | — | Supplies the controlled upload relationship. |
| `onChange` | `(value) => void` | — | Receives the updated upload relationship. |
| `allowCreate` | `boolean` | `false` | Shows file creation when permissions allow it. |
| `hasMany` | `boolean` | `false` | Allows multiple upload documents. |
| `displayPreview` | `boolean` | — | Displays thumbnails for selected uploads. |
| `maxRows` | `number` | — | Limits selections in multi-value mode. |
| `filterOptions` | `FilterOptionsResult` | — | Restricts documents shown in the List Drawer. |
| `readOnly` | `boolean` | `false` | Disables selection, removal, and upload intake. |
_\* An asterisk denotes that a prop is required._
## Provider requirements
These inputs are intended for Payload Custom Fields. They rely on the Admin Panel's Config, Auth, Translation, Locale, Modal, drawer, and document-event contexts. `UploadInput` also uses Payload's bulk-upload context. Use the complete `RelationshipField` or `UploadField` when Payload should manage the field configuration and form connection for you.
# ErrorPill
Source: https://payloadcms.com/docs/ui-components/error-pill
The `ErrorPill` component displays the number of validation errors associated with a field or group of fields. It returns `null` when the count is zero.
## Import
```tsx
import { ErrorPill, useTranslation } from '@payloadcms/ui'
```
## Counts
**Implementation**
```tsx
const { i18n } = useTranslation()
```
**Styling**
Override ErrorPill directly to match the project’s validation color, shape, and emphasis.
```css
.error-pill {
background: #dc2626;
color: #ffffff;
border-radius: 999px;
font-weight: 600;
}
```
- `background`: Error count surface.
- `color`: Error count text.
- `font-weight`: Count emphasis.
Pass the `i18n` object from `useTranslation`. Add `withMessage` when the count needs a localized “error” or “errors” label.
## Common props
These are the props most commonly used with this component. See its exported types in `@payloadcms/ui` for the complete list.
| Prop | Type | Default | Description |
| ------------- | ------------ | ------- | ------------------------------------------------ |
| `count` \* | `number` | — | Number of errors. Renders nothing when set to 0. |
| `i18n` \* | `I18nClient` | — | Supplies localized error labels. |
| `withMessage` | `boolean` | `false` | Displays “error” or “errors” after the count. |
| `className` | `string` | — | Adds a custom class to the element. |
_\* An asterisk denotes that a prop is required._
# Gutter
Source: https://payloadcms.com/docs/ui-components/gutter
The `Gutter` component applies the standard horizontal spacing used by Payload views. Use it to align custom view content with the surrounding Admin Panel.
## Import
```tsx
import { Gutter } from '@payloadcms/ui'
```
## Basic usage
**Implementation**
```tsx