1. Project Overview & Quickstart (payloadcms/payload)
File: README.md
**Explore the Docs** Β· **Community Help** Β· **Roadmap** Β· **View G2 Reviews**
[!IMPORTANT]
Star this repo or keep an eye on it to follow along.
Payload is the first-ever Next.js native CMS that can install directly in your existing /app folder. It's the start of a new era for headless CMS.
Benefits over a regular CMS
Quickstart
Before beginning to work with Payload, make sure you have all of the required software.
pnpx create-payload-app@latestIf you're new to Payload, you should start with the website template (pnpx create-payload-app@latest -t website). It shows how to do everything - including custom Rich Text blocks, on-demand revalidation, live preview, and more. It comes with a frontend built with Tailwind all in one /app folder.
One-click deployment options
You can deploy Payload serverlessly in one-click via Vercel and Cloudflareβgiving everything you need without the hassle of the plumbing.
Deploy on Cloudflare
Fully self-contained β one click to deploy Payload with Workers, R2 for uploads, and D1 for a globally replicated database.
Deploy on Vercel
All-in-one on Vercel β one click to deploy Payload with a Next.js front end, Neon database, and Vercel Blob for media storage.
One-click templates
Jumpstart your next project with a ready-to-go template. These are production-ready, end-to-end solutions designed to get you to market fast. Build any kind of website, ecommerce store, blog, or portfolio β complete with a modern front end built using React Server Components and Tailwind.
π [Website](https://github.com/payloadcms/payload/tree/main/templates/website)
ποΈ [Ecommerce](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) π _**NEW**_ π
We're constantly adding more templates to our Templates Directory.
If you maintain your own, add the payload-template topic to your GitHub repo so others can discover it.
π Explore more:
β¨ Payload Features
- Completely free and open-source
- Next.js native, built to run inside your
/appfolder - Use server components to extend Payload UI
- Query your database directly in server components, no need for REST / GraphQL
- Fully TypeScript with automatic types for your data
- Auth out of the box
- Versions and drafts
- Localization
- Block-based layout builder
- Customizable React admin
- Lexical rich text editor
- Conditional field logic
- Extremely granular Access Control
- Document and field-level hooks for every action Payload provides
- Intensely fast API
- Highly secure thanks to HTTP-only cookies, CSRF protection, and more
ποΈ Documentation
Check out the Payload website to find in-depth documentation for everything that Payload offers.
Migrating from v2 to v3? Check out the 3.0 Migration Guide on how to do it.
π Contributing
If you want to add contributions to this repository, please follow the instructions in contributing.md.
π Examples
The Examples Directory is a great resource for learning how to setup Payload in a variety of different ways, but you can also find great examples in our blog and throughout our social media.
If you'd like to run the examples, you can use create-payload-app to create a project from one:
npx create-payload-app --example example_nameYou can see more examples at:
π Plugins
Payload is highly extensible and allows you to install or distribute plugins that add or remove functionality. There are both officially-supported and community-supported plugins available. If you maintain your own plugin, consider adding the payload-plugin topic to your GitHub repository for others to find.
π¨ Need help?
There are lots of good conversations and resources in our Github Discussions board and our Discord Server. If you're struggling with something, chances are, someone's already solved what you're up against. :point_down:
β Like what we're doing? Give us a star
π Thanks to all our contributors
File: docs/access-control/collections.mdx
title: Collection Access Control label: Collections order: 20 desc: With Collection-level Access Control you can define which users can create, read, update or delete Collections. keywords: collections, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
Collection Access Control is Access Control used to restrict access to Documents within a Collection, as well as what they can and cannot see within the Admin Panel as it relates to that Collection.
To add Access Control to a Collection, use the access property in your Collection Config:
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:
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. |
read |
Used in the find and findByID operations. More details. |
update |
Used in the update operation. More details. |
delete |
Used in the delete operation. More details. |
If a Collection supports Authentication, the following additional options are available:
| Function | Allows/Denies Access |
|---|---|
admin |
Used to restrict access to the Admin Panel. More details. |
unlock |
Used to restrict which users can access the unlock operation. More details. |
If a Collection supports Versions, 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. |
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:
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 object containing the currently authenticated user. |
data |
The data passed to create the document with. |
slug |
The slug of the Collection being accessed. |
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:
import type { CollectionConfig } from 'payload'
export const CollectionWithReadAccess: CollectionConfig = {
// ...
access: {
// highlight-start
read: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
} Tip: Return a Query to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. More details.
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
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 object containing the currently authenticated user. |
id |
id of document requested, if within findByID. |
slug |
The slug of the Collection being accessed. |
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:
import type { CollectionConfig } from 'payload'
export const CollectionWithUpdateAccess: CollectionConfig = {
// ...
access: {
// highlight-start
update: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
} Tip: Return a Query to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. More details.
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
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 object containing the currently authenticated user. |
id |
id of document requested to update. |
data |
The data passed to update the document with. |
slug |
The slug of the Collection being accessed. |
Tip: For Collections with Drafts enabled, you can
use update access to control 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 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:
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:
import type { Access } from 'payload'
import type { Customer } from '@/payload-types'
export const canDeleteCustomer: Access<Customer> = 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 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 collections, check data.deletedAt to differentiate between soft delete and permanent delete operations. |
slug |
The slug of the Collection being accessed. |
Tip: For Collections with Trash enabled, you can use
the data argument to allow users to soft delete (trash) documents while
restricting permanent deletion. See Trash Access
Control for examples.
Admin
If the Collection is used to access the Admin Panel, 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:
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 object containing the currently authenticated user. |
slug |
The slug of the Collection being accessed. |
Unlock
Determines which users can unlock other users who may be blocked from authenticating successfully due to failing too many login attempts.
To add Unlock Access Control to a Collection, use the unlock property in the Collection Config:
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 object containing the currently authenticated user. |
slug |
The slug of the Collection being accessed. |
Read Versions
If the Collection has Versions 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:
import type { CollectionConfig } from 'payload'
export const CollectionWithVersionsAccess: CollectionConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
} Note: Returning a Query will apply the constraint
to the versions collection, not the
original Collection.
The following arguments are provided to the readVersions function:
| Option | Description |
|---|---|
req |
The Request object containing the currently authenticated user. |
slug |
The slug of the Collection being accessed. |
File: docs/access-control/fields.mdx
title: Field-level Access Control label: Fields order: 40 desc: Field-level Access Control is specified within a field's config, and allows you to define which users can create, read or update Fields. keywords: fields, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
Field Access Control is Access Control used to restrict access to specific Fields within a Document.
To add Access Control to a Field, use the access property in your Field Config:
import type { Field } from 'payload'
export const FieldWithAccessControl: Field = {
// ...
access: {
// highlight-line
// ...
},
} Note: Field Access Control does not support returning
Query constraints like Collection Access
Control 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:
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. |
read |
Allows or denies the ability to read a field's value. More details. |
update |
Allows or denies the ability to update a field's value More details. |
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 object containing the currently authenticated user |
collection |
The collection config that owns the field (present only for collection-owned fields). |
global |
The global config that owns the field (present only for global-owned fields). |
data |
The full data passed to create the document. |
siblingData |
Immediately adjacent field data passed to create the document. |
blockData |
The nearest parent block row data when this field is inside a block. |
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 object containing the currently authenticated user |
collection |
The collection config that owns the field (present only for collection-owned fields). |
global |
The global config that owns the field (present only for global-owned fields). |
id |
id of the document being read |
doc |
The full document data. |
siblingData |
Immediately adjacent field data of the document being read. |
blockData |
The nearest parent block row data when this field is inside a block. |
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 object containing the currently authenticated user |
collection |
The collection config that owns the field (present only for collection-owned fields). |
global |
The global config that owns the field (present only for global-owned fields). |
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. |
blockData |
The nearest parent block row data when this field is inside a block. |
File: docs/access-control/globals.mdx
title: Globals Access Control label: Globals order: 30 desc: Global-level Access Control is specified within each Global's `access` property and allows you to define which users can read or update Globals. keywords: globals, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
Global Access Control is Access Control used to restrict access to Global Documents, as well as what they can and cannot see within the Admin Panel as it relates to that Global.
To add Access Control to a Global, use the access property in your Global Config:
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, use the access property in the Global Config:
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 HeaderThe following options are available:
| Function | Allows/Denies Access |
|---|---|
read |
Used in the findOne Global operation. More details. |
update |
Used in the update Global operation. More details. |
If a Global supports Versions, 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
Returns a boolean result or optionally a query constraint which limits who can read this global based on its current properties.
To add read Access Control to a Global, use the access property in the Global Config:
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 object containing the currently authenticated user. |
slug |
The slug of the Global being accessed. |
Update
Returns a boolean result or optionally a query constraint which limits who can update this global based on its current properties.
To add update Access Control to a Global, use the access property in the Global Config:
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 object containing the currently authenticated user. |
data |
The data passed to update the global with. |
slug |
The slug of the Global being accessed. |
Read Versions
If the Global has Versions 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:
import type { GlobalConfig } from 'payload'
export const GlobalWithVersionsAccess: GlobalConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
},
} Note: Returning a Query will apply the constraint
to the versions collection, not the
original Global.
The following arguments are provided to the readVersions function:
| Option | Description |
|---|---|
req |
The Request object containing the currently authenticated user. |
slug |
The slug of the Global being accessed. |
File: docs/access-control/overview.mdx
title: Access Control label: Overview order: 10 desc: Payload makes it simple to define and manage access control. By declaring roles, you can set permissions and restrict what your users can interact with. keywords: overview, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, express
Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a boolean or a query constraint to restrict which documents users can interact with.
Example use cases:
- Allowing anyone
readaccess to allPosts - Only allowing public access to
Posts where astatusfield is equal topublished - Giving only
Users with arolefield equal toadminthe ability to deletePage(s) - Allowing anyone to create
ContactSubmissions, but only logged in users toread,updateordeletethem - Restricting a
Userto only be able to see their ownOrder(s), but no others - Allowing
Users that belong to a certainOrganizationto access only thatOrganization'sResources
Default Settings
By default, all Collections and Globals require that a user is logged in to be able to interact in any way. The default Access Control function evaluates the user from the Express req and returns true if a user is logged in, and false if not.
Default Access function:
const defaultPayloadAccess = ({ req: { user } }) => {
// Return `true` if a user is found
// and `false` if it is undefined or null
return Boolean(user);
} Note:
In the Local API, all Access Control functions are skipped by default, allowing your server to do whatever it needs. But, you can opt back in by setting the option overrideAccess to false.
Access Control Types
You can manage access within Payload on three different levels:
When Access Control is Executed
Note:
Access control functions are utilized in two places. It's important to understand how and when your access control is executed.
As you execute operations
When you perform Payload operations like create, read, update, and delete, your access control functions will be executed before any changes or operations are completed.
Within the Admin UI
The Payload Admin UI responds dynamically to the access control that you define. For example, if you restrict editing a ExampleCollection to only users that feature a role of admin, the Payload Admin UI will hide the ExampleCollection from the Admin UI entirely. This is super powerful and allows you to control who can do what with your Admin UI.
To accomplish this, Payload ships with an Access operation, which is executed when a user logs into the Admin UI. Payload will execute each one of your access control functions, across all collections, globals, and fields, at the top level and return a response that contains a reflection of what the currently authenticated user can do with your application.
Argument Availability
Important:
When your access control functions are executed via the access operation, the id and data arguments will be undefined, because Payload is executing your functions without referencing a specific document.
If you use id or data within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your access control is being executed via the access operation, to determine solely what the user can do within the Admin UI.
File: docs/admin/locked-documents.mdx
title: Document Locking label: Document Locking order: 40 desc: Ensure your documents are locked during editing to prevent concurrent changes from multiple users and maintain data integrity. keywords: locking, document locking, edit locking, document, concurrency, Payload, headless, Content Management System, cms, javascript, react, node, nextjs
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:
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.
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.
File: docs/admin/metadata.mdx
title: Page Metadata label: Metadata order: 70 desc: Customize the metadata of your pages within the Admin Panel keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
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 function. This is used to generate the <head> 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:
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:
{
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Admin Panel',
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. |
Reminder: These are the root-level options for the Admin Panel. You can
also customize metadata on the Collection,
Global, and Document levels through their
respective configs.
Icons
The Icons Config corresponds to the <link> 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:
{
// ...
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.
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:
{
// ...
admin: {
meta: {
// highlight-start
openGraph: {
// highlight-end
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.
Robots
Setting the robots property will allow you to control the robots meta tag that is rendered within the <head> 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:
{
// ...
admin: {
meta: {
// highlight-start
robots: 'noindex, nofollow',
// highlight-end
},
},
}For a full list of all available Robots options, see the Next.js documentation.
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.
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:
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Collection',
},
},
}The Collection Meta config has the same options as the 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:
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Global',
},
},
}The Global Meta config has the same options as the Root Metadata config.
View Metadata
View Metadata is the metadata that is applied to specific Views within the Admin Panel. This metadata is used to customize the title and description of a specific view, overriding any metadata set at the Root, Collection, or Global level.
To customize View Metadata, use the meta key within your View Config:
{
// ...
admin: {
views: {
dashboard: {
// highlight-start
meta: {
// highlight-end
title: 'My Dashboard',
}
},
},
},
}File: docs/admin/overview.mdx
title: The Admin Panel label: Overview order: 10 desc: Manage your data and customize the Admin Panel by swapping in your own React components. Create, modify or remove views, fields, styles and much more. keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express
Payload dynamically generates a beautiful, fully functional React admin panel to manage your data. It's extremely powerful and can be customized / extended upon easily by swapping in your own React components. You can add additional views, modify how built-in views look / work, swap out Payload branding for your client's, build your own field types and much more.
The Payload Admin panel is built with Webpack, code-split, highly performant (even with 100+ fields), and written fully in TypeScript.
The Admin panel is meant to be simple enough to give you a starting point but
not bring too much complexity, so that you can easily customize it to suit the
needs of your application and your editors.
Screenshot of the Admin panel while editing a document from an example AllFields collection
Admin Options
All options for the Admin panel are defined in your base Payload config file.
| Option | Description |
|---|---|
user |
The slug of a Collection that you want be used to log in to the Admin dashboard. More |
buildPath |
Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to path.resolve(process.cwd(), 'build'). |
meta |
Base meta data to use for the Admin panel. Included properties are titleSuffix, ogImage, and favicon. |
disable |
If set to true, the entire Admin panel will be disabled. |
indexHTML |
Optionally replace the entirety of the index.html file used by the Admin panel. Reference the base index.html file to ensure your replacement has the appropriate HTML elements. |
css |
Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. More. |
scss |
Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. More. |
dateFormat |
Global date format that will be used for all dates in the Admin panel. Any valid date-fns format pattern can be used. |
avatar |
Set account profile picture. Options: gravatar, default or a custom React component. |
autoLogin |
Used to automate admin log-in for dev and demonstration convenience. More. |
components |
Component overrides that affect the entirety of the Admin panel. More |
webpack |
Customize the Webpack config that's used to generate the Admin panel. More |
logoutRoute |
The route for the logout page. |
inactivityRoute |
The route for the logout inactivity page. |
The Admin User Collection
Important:
The Payload Admin panel can only be used by one Collection that supports
Authentication.
To specify which Collection to use to log in to the Admin panel, pass the admin options a user key equal to the slug of the Collection that you'd like to use.
payload.config.js:
import { buildConfig } from "payload/config";
const config = buildConfig({
admin: {
user: "admins", // highlight-line
},
});By default, if you have not specified a Collection, Payload will automatically provide you with a User Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default User Collection by passing your own collection using users as its slug to Payload. When this is done, Payload will use your provided User Collection instead of its default version.
Note: you can use whatever Collection you'd like to access the Admin panel as long as the Collection supports Authentication. It doesn't need to be called users!
For example, you may wish to have two Collections that both support Authentication:
admins- meant to have a higher level of permissions to manage your data and access the Admin panelcustomers- meant for end users of your app that should not be allowed to log into the Admin panel
This is totally possible. For the above scenario, by specifying admin: { user: 'admins' }, your Payload Admin panel will use admins. Any users logged in as customers will not be able to log in via the Admin panel.
Light and dark modes
Users in the admin panel have access to choosing between light mode and dark mode for their editing experience. The setting is managed while logged into the admin UI within the user account page and will be stored with the browser. By default, the operating system preference is detected and used.
Restricting user access
If you would like to restrict which users from a single Collection can access the Admin panel, you can use the admin access control function. Click here to learn more.
File: docs/admin/preferences.mdx
title: Managing User Preferences label: Preferences order: 50 desc: Store the preferences of your users as they interact with the Admin panel. keywords: admin, preferences, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express
As your users interact with your Admin panel, you might want to store their preferences in a persistent manner, so that when they revisit the Admin panel, they can pick right back up where they left off.
Out of the box, Payload handles the persistence of your users' preferences in a handful of ways, including:
Collection
Listview active columns, and their order, that users defineTheir last active locale
The "collapsed" state of blocks, on a document level, as users edit or interact with documents
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
Navcomponent, and you've built in an "accordion-style" UI, you might want to store thecollapsedstate of each Nav collapsible item. This way, if an editor returns to the panel, theirNavstate is persisted automatically - You might want to store
recentlyAccesseddocuments to give admin editors an easy shortcut back to their recently accessed documents on theDashboardor 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 _preferences collection that stores user preferences. Each document in the _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 |
The ID of the user that is storing its preference. |
userCollection |
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 and REST 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: thekeyof 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: thekeyof your preference to set.value: thevalueof 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.
/* Detailed source-code truncated for AI context efficiency. */2. Official Technical Reference & Guides (payloadcms/website)
Payload Website
This is the repository for Payload's official website. It was built completely in public using Payload itself, more on that here.
This site showcases lots of cool stuff like how to use Next.js 15 + Payload's local API to its fullest extent, how to build a super dynamic light / dark mode into a Next site without any first-load flickering, how to render remotely stored docs from MDX to Next.js pages using just Payload (no external libraries), how to use Stripe to build a custom SaaS integration, and much more.
β¨ Tech stack
- Payload (obviously)
- TypeScript
- Next.js 15 and its new App Router
- SCSS Modules
- MDX for docs, using the Lexical MDX Converter
- GraphQL for Payload Cloud
- Stripe for Payload Cloud
β The CMS
Payload is leveraged for everything that this site does, outside of its documentation which is all stored as Markdown in the Payload repo on GitHub. Both the CMS and the website frontend are found within the same app folder.
βοΈ Payload Cloud
This repo contains the source code for Payload Cloud. This is a one-click integration to deploy production-ready instances of your Payload apps directly from your GitHub repo, read the blog post to get all the details. The entire frontend of Payload Cloud has been built in public and is included within this repo π±.
π Running the project locally
To get started with this repo locally, follow the steps below:
- Clone the repo
pnpm i- Run
cp .env.example .envto create an.envfile - Fill out the values within your new
.env, corresponding to your own environment - Run
pnpm dev - Bam
Hosts file
The locally running app must run on local.payloadcms.com:3000 because of http-only cookie policies and how the GitHub App redirects the user back to the site after authenticating. To do this, you'll need to add the following to your hosts file:
127.0.0.1 local.payloadcms.comOn Mac you can find the hosts file at
/etc/hosts. On Windows, it's atC:\Windows\System32\drivers\etc\hosts:
Documentation
The documentation for this site is stored in the Payload repo as Markdown files. These are fetched when you press the "Sync Docs" button in the CMS. Pressing that button does the following:
- Docs are pulled from the Payload repo on GitHub.
- The docs are converted from MDX to Lexical and stored in the CMS.
- The frontend docs pages are revalidated.
- Visiting the docs pages will pull the latest docs from the CMS, and render those lexical nodes to JSX.
Working on the docs locally - GitHub
By default, the docs are pulled from the main branch of the Payload repo on GitHub. You can load the docs for a different branch by opening the /docs/dynamic/ route on the website. This will dynamically load them every time you visit the page, without needing to sync them in the CMS.
Example:
- This pulls from the main branch: https://payloadcms.com/docs/getting-started/concepts
- This pulls from the feat/myfeature branch: https://payloadcms.com/docs/dynamic/getting-started/concepts?branch=feat/myfeature
In order to edit docs for that branch without touching markdown files, you can use the branch selector in the CMS to select the branch you want to work on. After making changes and saving the document, the lexical docs will be converted to MDX and pushed to the selected branch on GitHub.
You will need to set the following environment variables to work with the GitHub sync:
// .env
# For reading from GitHub
GITHUB_ACCESS_TOKEN=ghp_
GITHUB_CLIENT_SECRET=
# For writing to GitHub - you can run the https://github.com/payloadcms/gh-commit repo locally
COMMIT_DOCS_API_URL=
COMMIT_DOCS_API_KEY=Working on docs locally - local markdown files
If you have the docs stored locally as markdown files and would like to preview them in the website, you can use the /docs/local/ route in the website. First, you need to set the DOCS_DIR_V3 environment variable to point to your local docs directory.
// .env
DOCS_DIR_V3=/documents/github/payload/docsThen, just open the /docs/local/ route: http://localhost:3000/docs/local/getting-started/concepts.
Every time you make a change to the markdown files, just reload the page to see the changes reflected. The local MDX files are read, automatically converted to lexical on-the-fly, and rendered in the website. This process will not make any changes to the database.
Beta and Legacy environment flags
You can also specify a beta version and legacy version to render different versions of the docs:
- Set the environment variable
NEXT_PUBLIC_ENABLE_BETA_DOCStotrueto enable the beta docs. - Specify a branch, commit, or tag with
NEXT_PUBLIC_BETA_DOCS_REF. The default for the beta docs isbeta. - Set the environment variable
NEXT_PUBLIC_ENABLE_LEGACY_DOCStotrueto enable the legacy docs. - Specify a branch, commit, or tag with
NEXT_PUBLIC_LEGACY_DOCS_REF. The default for the legacy docs isnull, and will fallback to themainbranch.
License
The Payload website is available as open source under the terms of the MIT license.