Payload

Build and debug Payload CMS projects with TypeScript-first architecture

✨ The solution you've been looking for

Verified
Tested and verified by our team
39912 Stars

Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.

payload-cms nextjs typescript cms database api authentication content-management
Repository

See It In Action

Interactive preview & real-world examples

Live Demo
Skill Demo Animation

AI Conversation Simulator

See how users interact with this skill

User Prompt

Help me create a blog collection with posts, authors, and categories. Posts should have drafts, rich text content, and SEO fields.

Skill Processing

Analyzing request...

Agent Response

Complete collection configurations with proper TypeScript types, field definitions, and relationship setup

Quick Start (3 Steps)

Get up and running in minutes

1

Install

claude-code skill install payload

claude-code skill install payload
2

Config

3

First Trigger

@payload help

Commands

CommandDescriptionRequired Args
@payload content-model-designDefine collections, fields, and relationships for your CMS structureNone
@payload access-control-implementationSet up role-based permissions and row-level security for contentNone
@payload hook-and-validation-logicImplement business logic with beforeChange, afterChange, and validation hooksNone

Typical Use Cases

Content Model Design

Define collections, fields, and relationships for your CMS structure

Access Control Implementation

Set up role-based permissions and row-level security for content

Hook and Validation Logic

Implement business logic with beforeChange, afterChange, and validation hooks

Overview

Payload CMS Application Development

Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.

Quick Reference

TaskSolutionDetails
Auto-generate slugsslugField()FIELDS.md#slug-field-helper
Restrict content by userAccess control with queryACCESS-CONTROL.md#row-level-security-with-complex-queries
Local API user opsuser + overrideAccess: falseQUERIES.md#access-control-in-local-api
Draft/publish workflowversions: { drafts: true }COLLECTIONS.md#versioning–drafts
Computed fieldsvirtual: true with afterReadFIELDS.md#virtual-fields
Conditional fieldsadmin.conditionFIELDS.md#conditional-fields
Custom field validationvalidate functionFIELDS.md#validation
Filter relationship listfilterOptions on fieldFIELDS.md#relationship
Select specific fieldsselect parameterQUERIES.md#field-selection
Auto-set author/datesbeforeChange hookHOOKS.md#collection-hooks
Prevent hook loopsreq.context checkHOOKS.md#context
Cascading deletesbeforeDelete hookHOOKS.md#collection-hooks
Geospatial queriespoint field with near/withinFIELDS.md#point-geolocation
Reverse relationshipsjoin field typeFIELDS.md#join-fields
Next.js revalidationContext control in afterChangeHOOKS.md#nextjs-revalidation-with-context-control
Query by relationshipNested property syntaxQUERIES.md#nested-properties
Complex queriesAND/OR logicQUERIES.md#andor-logic
TransactionsPass req to operationsADAPTERS.md#threading-req-through-operations
Background jobsJobs queue with tasksADVANCED.md#jobs-queue
Custom API routesCollection custom endpointsADVANCED.md#custom-endpoints
Cloud storageStorage adapter pluginsADAPTERS.md#storage-adapters
Multi-languagelocalization config + localized: trueADVANCED.md#localization
Create plugin(options) => (config) => ConfigPLUGIN-DEVELOPMENT.md#plugin-architecture
Plugin package setupPackage structure with SWCPLUGIN-DEVELOPMENT.md#plugin-package-structure
Add fields to collectionMap collections, spread fieldsPLUGIN-DEVELOPMENT.md#adding-fields-to-collections
Plugin hooksPreserve existing hooks in arrayPLUGIN-DEVELOPMENT.md#adding-hooks
Check field typeType guard functionsFIELD-TYPE-GUARDS.md

Quick Start

1npx create-payload-app@latest my-app
2cd my-app
3pnpm dev

Minimal Config

 1import { buildConfig } from 'payload'
 2import { mongooseAdapter } from '@payloadcms/db-mongodb'
 3import { lexicalEditor } from '@payloadcms/richtext-lexical'
 4import path from 'path'
 5import { fileURLToPath } from 'url'
 6
 7const filename = fileURLToPath(import.meta.url)
 8const dirname = path.dirname(filename)
 9
10export default buildConfig({
11  admin: {
12    user: 'users',
13    importMap: {
14      baseDir: path.resolve(dirname),
15    },
16  },
17  collections: [Users, Media],
18  editor: lexicalEditor(),
19  secret: process.env.PAYLOAD_SECRET,
20  typescript: {
21    outputFile: path.resolve(dirname, 'payload-types.ts'),
22  },
23  db: mongooseAdapter({
24    url: process.env.DATABASE_URL,
25  }),
26})

Essential Patterns

Basic Collection

 1import type { CollectionConfig } from 'payload'
 2
 3export const Posts: CollectionConfig = {
 4  slug: 'posts',
 5  admin: {
 6    useAsTitle: 'title',
 7    defaultColumns: ['title', 'author', 'status', 'createdAt'],
 8  },
 9  fields: [
10    { name: 'title', type: 'text', required: true },
11    { name: 'slug', type: 'text', unique: true, index: true },
12    { name: 'content', type: 'richText' },
13    { name: 'author', type: 'relationship', relationTo: 'users' },
14  ],
15  timestamps: true,
16}

For more collection patterns (auth, upload, drafts, live preview), see COLLECTIONS.md.

Common Fields

 1// Text field
 2{ name: 'title', type: 'text', required: true }
 3
 4// Relationship
 5{ name: 'author', type: 'relationship', relationTo: 'users', required: true }
 6
 7// Rich text
 8{ name: 'content', type: 'richText', required: true }
 9
10// Select
11{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }
12
13// Upload
14{ name: 'image', type: 'upload', relationTo: 'media' }

For all field types (array, blocks, point, join, virtual, conditional, etc.), see FIELDS.md.

Hook Example

 1export const Posts: CollectionConfig = {
 2  slug: 'posts',
 3  hooks: {
 4    beforeChange: [
 5      async ({ data, operation }) => {
 6        if (operation === 'create') {
 7          data.slug = slugify(data.title)
 8        }
 9        return data
10      },
11    ],
12  },
13  fields: [{ name: 'title', type: 'text' }],
14}

For all hook patterns, see HOOKS.md. For access control, see ACCESS-CONTROL.md.

Access Control with Type Safety

 1import type { Access } from 'payload'
 2import type { User } from '@/payload-types'
 3
 4// Type-safe access control
 5export const adminOnly: Access = ({ req }) => {
 6  const user = req.user as User
 7  return user?.roles?.includes('admin') || false
 8}
 9
10// Row-level access control
11export const ownPostsOnly: Access = ({ req }) => {
12  const user = req.user as User
13  if (!user) return false
14  if (user.roles?.includes('admin')) return true
15
16  return {
17    author: { equals: user.id },
18  }
19}

Query Example

 1// Local API
 2const posts = await payload.find({
 3  collection: 'posts',
 4  where: {
 5    status: { equals: 'published' },
 6    'author.name': { contains: 'john' },
 7  },
 8  depth: 2,
 9  limit: 10,
10  sort: '-createdAt',
11})
12
13// Query with populated relationships
14const post = await payload.findByID({
15  collection: 'posts',
16  id: '123',
17  depth: 2, // Populates relationships (default is 2)
18})
19// Returns: { author: { id: "user123", name: "John" } }
20
21// Without depth, relationships return IDs only
22const post = await payload.findByID({
23  collection: 'posts',
24  id: '123',
25  depth: 0,
26})
27// Returns: { author: "user123" }

For all query operators and REST/GraphQL examples, see QUERIES.md.

Getting Payload Instance

 1// In API routes (Next.js)
 2import { getPayload } from 'payload'
 3import config from '@payload-config'
 4
 5export async function GET() {
 6  const payload = await getPayload({ config })
 7
 8  const posts = await payload.find({
 9    collection: 'posts',
10  })
11
12  return Response.json(posts)
13}
14
15// In Server Components
16import { getPayload } from 'payload'
17import config from '@payload-config'
18
19export default async function Page() {
20  const payload = await getPayload({ config })
21  const { docs } = await payload.find({ collection: 'posts' })
22
23  return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
24}

Security Pitfalls

1. Local API Access Control (CRITICAL)

By default, Local API operations bypass ALL access control, even when passing a user.

 1// ❌ SECURITY BUG: Passes user but ignores their permissions
 2await payload.find({
 3  collection: 'posts',
 4  user: someUser, // Access control is BYPASSED!
 5})
 6
 7// ✅ SECURE: Actually enforces the user's permissions
 8await payload.find({
 9  collection: 'posts',
10  user: someUser,
11  overrideAccess: false, // REQUIRED for access control
12})

When to use each:

  • overrideAccess: true (default) - Server-side operations you trust (cron jobs, system tasks)
  • overrideAccess: false - When operating on behalf of a user (API routes, webhooks)

See QUERIES.md#access-control-in-local-api.

2. Transaction Failures in Hooks

Nested operations in hooks without req break transaction atomicity.

 1// ❌ DATA CORRUPTION RISK: Separate transaction
 2hooks: {
 3  afterChange: [
 4    async ({ doc, req }) => {
 5      await req.payload.create({
 6        collection: 'audit-log',
 7        data: { docId: doc.id },
 8        // Missing req - runs in separate transaction!
 9      })
10    },
11  ]
12}
13
14// ✅ ATOMIC: Same transaction
15hooks: {
16  afterChange: [
17    async ({ doc, req }) => {
18      await req.payload.create({
19        collection: 'audit-log',
20        data: { docId: doc.id },
21        req, // Maintains atomicity
22      })
23    },
24  ]
25}

See ADAPTERS.md#threading-req-through-operations.

3. Infinite Hook Loops

Hooks triggering operations that trigger the same hooks create infinite loops.

 1// ❌ INFINITE LOOP
 2hooks: {
 3  afterChange: [
 4    async ({ doc, req }) => {
 5      await req.payload.update({
 6        collection: 'posts',
 7        id: doc.id,
 8        data: { views: doc.views + 1 },
 9        req,
10      }) // Triggers afterChange again!
11    },
12  ]
13}
14
15// ✅ SAFE: Use context flag
16hooks: {
17  afterChange: [
18    async ({ doc, req, context }) => {
19      if (context.skipHooks) return
20
21      await req.payload.update({
22        collection: 'posts',
23        id: doc.id,
24        data: { views: doc.views + 1 },
25        context: { skipHooks: true },
26        req,
27      })
28    },
29  ]
30}

See HOOKS.md#context.

Project Structure

 1src/
 2├── app/
 3│   ├── (frontend)/
 4│   │   └── page.tsx
 5│   └── (payload)/
 6│       └── admin/[[...segments]]/page.tsx
 7├── collections/
 8│   ├── Posts.ts
 9│   ├── Media.ts
10│   └── Users.ts
11├── globals/
12│   └── Header.ts
13├── components/
14│   └── CustomField.tsx
15├── hooks/
16│   └── slugify.ts
17└── payload.config.ts

Type Generation

 1// payload.config.ts
 2export default buildConfig({
 3  typescript: {
 4    outputFile: path.resolve(dirname, 'payload-types.ts'),
 5  },
 6  // ...
 7})
 8
 9// Usage
10import type { Post, User } from '@/payload-types'

Reference Documentation

  • FIELDS.md - All field types, validation, admin options
  • FIELD-TYPE-GUARDS.md - Type guards for runtime field type checking and narrowing
  • COLLECTIONS.md - Collection configs, auth, upload, drafts, live preview
  • HOOKS.md - Collection hooks, field hooks, context patterns
  • ACCESS-CONTROL.md - Collection, field, global access control, RBAC, multi-tenant
  • ACCESS-CONTROL-ADVANCED.md - Context-aware, time-based, subscription-based access, factory functions, templates
  • QUERIES.md - Query operators, Local/REST/GraphQL APIs
  • ENDPOINTS.md - Custom API endpoints: authentication, helpers, request/response patterns
  • ADAPTERS.md - Database, storage, email adapters, transactions
  • ADVANCED.md - Authentication, jobs, endpoints, components, plugins, localization
  • PLUGIN-DEVELOPMENT.md - Plugin architecture, monorepo structure, patterns, best practices

Resources

What Users Are Saying

Real feedback from the community

Environment Matrix

Dependencies

Node.js 18+
TypeScript 4.9+
Next.js 14+
Database adapter (MongoDB/PostgreSQL)

Framework Support

Next.js ✓ (recommended) React ✓ TypeScript ✓ (recommended) MongoDB ✓ PostgreSQL ✓

Context Window

Token Usage ~5K-15K tokens for complex configurations and debugging

Security & Privacy

Information

Author
payloadcms
Updated
2026-01-30
Category
productivity-tools