payload-cms

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Payload CMS Development

Payload CMS 开发

Payload is a Next.js native CMS with TypeScript-first architecture. This skill transfers expert knowledge for building collections, hooks, access control, and queries the right way.
Payload 是一款基于 Next.js 的原生 CMS,采用 TypeScript 优先的架构。本内容分享了正确构建 collections、hooks、访问控制和查询的专业知识。

Mental Model

思维模型

Think of Payload as three interconnected layers:
  1. Config Layer → Collections, globals, fields define your schema
  2. Hook Layer → Lifecycle events transform and validate data
  3. Access Layer → Functions control who can do what
Every operation flows through:
Config → Access Check → Hook Chain → Database → Response Hooks
可以将 Payload 视为 三个相互关联的层级
  1. 配置层 → Collections、全局配置、fields 定义你的数据模型
  2. 钩子层 → 生命周期事件用于转换和验证数据
  3. 访问层 → 函数控制用户操作权限
所有操作流程为:
配置 → 访问检查 → 钩子链 → 数据库 → 响应钩子

Quick Reference

快速参考

TaskSolutionDetails
Auto-generate slugs
slugField()
or beforeChange hook
[references/fields.md#slug-field]
Restrict by userAccess control with query constraint[references/access-control.md]
Local API with auth
user
+
overrideAccess: false
[references/queries.md#local-api]
Draft/publish
versions: { drafts: true }
[references/collections.md#drafts]
Computed fields
virtual: true
with afterRead hook
[references/fields.md#virtual]
Conditional fields
admin.condition
[references/fields.md#conditional]
Filter relationships
filterOptions
on field
[references/fields.md#relationship]
Prevent hook loops
req.context
flag
[references/hooks.md#context]
TransactionsPass
req
to all operations
[references/hooks.md#transactions]
Background jobsJobs queue with tasks[references/advanced.md#jobs]
任务解决方案详情
自动生成别名
slugField()
或 beforeChange 钩子
[references/fields.md#slug-field]
按用户限制访问带查询约束的访问控制[references/access-control.md]
带认证的本地 API
user
+
overrideAccess: false
[references/queries.md#local-api]
草稿/发布功能
versions: { drafts: true }
[references/collections.md#drafts]
计算字段
virtual: true
搭配 afterRead 钩子
[references/fields.md#virtual]
条件字段
admin.condition
[references/fields.md#conditional]
过滤关联数据字段上的
filterOptions
[references/fields.md#relationship]
避免钩子循环
req.context
标记
[references/hooks.md#context]
事务处理为所有操作传递
req
[references/hooks.md#transactions]
后台任务带任务的作业队列[references/advanced.md#jobs]

Quick Start

快速开始

bash
npx create-payload-app@latest my-app
cd my-app
pnpm dev
bash
npx create-payload-app@latest my-app
cd my-app
pnpm dev

Minimal Config

最简配置

ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export default buildConfig({
  admin: { user: 'users' },
  collections: [Users, Media, Posts],
  editor: lexicalEditor(),
  secret: process.env.PAYLOAD_SECRET,
  typescript: { outputFile: 'payload-types.ts' },
  db: mongooseAdapter({ url: process.env.DATABASE_URL }),
})
ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export default buildConfig({
  admin: { user: 'users' },
  collections: [Users, Media, Posts],
  editor: lexicalEditor(),
  secret: process.env.PAYLOAD_SECRET,
  typescript: { outputFile: 'payload-types.ts' },
  db: mongooseAdapter({ url: process.env.DATABASE_URL }),
})

Core Patterns

核心模式

Collection Definition

集合定义

ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'author', 'status', 'createdAt'],
  },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text', unique: true, index: true },
    { name: 'content', type: 'richText' },
    { name: 'author', type: 'relationship', relationTo: 'users' },
    { name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
  ],
  timestamps: true,
}
ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'author', 'status', 'createdAt'],
  },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text', unique: true, index: true },
    { name: 'content', type: 'richText' },
    { name: 'author', type: 'relationship', relationTo: 'users' },
    { name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
  ],
  timestamps: true,
}

Hook Pattern (Auto-slug)

钩子模式(自动生成别名)

ts
export const Posts: CollectionConfig = {
  slug: 'posts',
  hooks: {
    beforeChange: [
      async ({ data, operation }) => {
        if (operation === 'create' && data.title) {
          data.slug = data.title.toLowerCase().replace(/\s+/g, '-')
        }
        return data
      },
    ],
  },
  fields: [{ name: 'title', type: 'text', required: true }],
}
ts
export const Posts: CollectionConfig = {
  slug: 'posts',
  hooks: {
    beforeChange: [
      async ({ data, operation }) => {
        if (operation === 'create' && data.title) {
          data.slug = data.title.toLowerCase().replace(/\s+/g, '-')
        }
        return data
      },
    ],
  },
  fields: [{ name: 'title', type: 'text', required: true }],
}

Access Control Pattern

访问控制模式

ts
import type { Access } from 'payload'

// Type-safe: admin-only access
export const adminOnly: Access = ({ req }) => {
  return req.user?.roles?.includes('admin') ?? false
}

// Row-level: users see only their own posts
export const ownPostsOnly: Access = ({ req }) => {
  if (!req.user) return false
  if (req.user.roles?.includes('admin')) return true
  return { author: { equals: req.user.id } }
}
ts
import type { Access } from 'payload'

// 类型安全:仅管理员可访问
export const adminOnly: Access = ({ req }) => {
  return req.user?.roles?.includes('admin') ?? false
}

// 行级权限:用户仅能查看自己的文章
export const ownPostsOnly: Access = ({ req }) => {
  if (!req.user) return false
  if (req.user.roles?.includes('admin')) return true
  return { author: { equals: req.user.id } }
}

Query Pattern

查询模式

ts
// Local API with access control
const posts = await payload.find({
  collection: 'posts',
  where: {
    status: { equals: 'published' },
    'author.name': { contains: 'john' },
  },
  depth: 2,
  limit: 10,
  sort: '-createdAt',
  user: req.user,
  overrideAccess: false, // CRITICAL: enforce permissions
})
ts
// 带访问控制的本地 API
const posts = await payload.find({
  collection: 'posts',
  where: {
    status: { equals: 'published' },
    'author.name': { contains: 'john' },
  },
  depth: 2,
  limit: 10,
  sort: '-createdAt',
  user: req.user,
  overrideAccess: false, // 关键:强制执行权限
})

Critical Security Rules

关键安全规则

1. Local API Access Control

1. 本地 API 访问控制

Default behavior bypasses ALL access control. This is the #1 security mistake.
ts
// ❌ SECURITY BUG: Access control bypassed even with user
await payload.find({ collection: 'posts', user: someUser })

// ✅ SECURE: Explicitly enforce permissions
await payload.find({
  collection: 'posts',
  user: someUser,
  overrideAccess: false, // REQUIRED
})
Rule: Use
overrideAccess: false
for any operation acting on behalf of a user.
默认行为会绕过所有访问控制,这是最常见的安全漏洞。
ts
// ❌ 安全漏洞:即使传入 user 也会绕过访问控制
await payload.find({ collection: 'posts', user: someUser })

// ✅ 安全:显式强制执行权限
await payload.find({
  collection: 'posts',
  user: someUser,
  overrideAccess: false, // 必须设置
})
规则:任何代表用户执行的操作都要使用
overrideAccess: false

2. Transaction Integrity

2. 事务完整性

Operations without
req
run in separate transactions.
ts
// ❌ DATA CORRUPTION: Separate transaction
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.create({
      collection: 'audit-log',
      data: { docId: doc.id },
      // Missing req - breaks atomicity!
    })
  }]
}

// ✅ ATOMIC: Same transaction
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.create({
      collection: 'audit-log',
      data: { docId: doc.id },
      req, // Maintains transaction
    })
  }]
}
Rule: Always pass
req
to nested operations in hooks.
未传入
req
的操作会在独立事务中运行
ts
// ❌ 数据损坏:独立事务
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.create({
      collection: 'audit-log',
      data: { docId: doc.id },
      // 缺少 req - 破坏原子性!
    })
  }]
}

// ✅ 原子操作:同一事务
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.create({
      collection: 'audit-log',
      data: { docId: doc.id },
      req, // 维持事务
    })
  }]
}
规则:在钩子中执行嵌套操作时,始终传递
req

3. Infinite Hook Loops

3. 避免无限钩子循环

Hooks triggering themselves create infinite loops.
ts
// ❌ INFINITE LOOP
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.update({
      collection: 'posts',
      id: doc.id,
      data: { views: doc.views + 1 },
      req,
    }) // Triggers afterChange again!
  }]
}

// ✅ SAFE: Context flag breaks the loop
hooks: {
  afterChange: [async ({ doc, req, context }) => {
    if (context.skipViewUpdate) return
    await req.payload.update({
      collection: 'posts',
      id: doc.id,
      data: { views: doc.views + 1 },
      req,
      context: { skipViewUpdate: true },
    })
  }]
}
钩子触发自身会导致无限循环
ts
// ❌ 无限循环
hooks: {
  afterChange: [async ({ doc, req }) => {
    await req.payload.update({
      collection: 'posts',
      id: doc.id,
      data: { views: doc.views + 1 },
      req,
    }) // 会再次触发 afterChange!
  }]
}

// ✅ 安全:上下文标记打破循环
hooks: {
  afterChange: [async ({ doc, req, context }) => {
    if (context.skipViewUpdate) return
    await req.payload.update({
      collection: 'posts',
      id: doc.id,
      data: { views: doc.views + 1 },
      req,
      context: { skipViewUpdate: true },
    })
  }]
}

Project Structure

项目结构

src/
├── app/
│   ├── (frontend)/page.tsx
│   └── (payload)/admin/[[...segments]]/page.tsx
├── collections/
│   ├── Posts.ts
│   ├── Media.ts
│   └── Users.ts
├── globals/Header.ts
├── hooks/slugify.ts
└── payload.config.ts
src/
├── app/
│   ├── (frontend)/page.tsx
│   └── (payload)/admin/[[...segments]]/page.tsx
├── collections/
│   ├── Posts.ts
│   ├── Media.ts
│   └── Users.ts
├── globals/Header.ts
├── hooks/slugify.ts
└── payload.config.ts

Type Generation

类型生成

Generate types after schema changes:
ts
// payload.config.ts
export default buildConfig({
  typescript: { outputFile: 'payload-types.ts' },
})

// Usage
import type { Post, User } from '@/payload-types'
修改数据模型后生成类型:
ts
// payload.config.ts
export default buildConfig({
  typescript: { outputFile: 'payload-types.ts' },
})

// 使用示例
import type { Post, User } from '@/payload-types'

Getting Payload Instance

获取 Payload 实例

ts
// In API routes
import { getPayload } from 'payload'
import config from '@payload-config'

export async function GET() {
  const payload = await getPayload({ config })
  const posts = await payload.find({ collection: 'posts' })
  return Response.json(posts)
}

// In Server Components
export default async function Page() {
  const payload = await getPayload({ config })
  const { docs } = await payload.find({ collection: 'posts' })
  return <div>{docs.map(p => <h1 key={p.id}>{p.title}</h1>)}</div>
}
ts
// 在 API 路由中
import { getPayload } from 'payload'
import config from '@payload-config'

export async function GET() {
  const payload = await getPayload({ config })
  const posts = await payload.find({ collection: 'posts' })
  return Response.json(posts)
}

// 在服务端组件中
export default async function Page() {
  const payload = await getPayload({ config })
  const { docs } = await payload.find({ collection: 'posts' })
  return <div>{docs.map(p => <h1 key={p.id}>{p.title}</h1>)}</div>
}

Common Field Types

常见字段类型

ts
// Text
{ name: 'title', type: 'text', required: true }

// Relationship
{ name: 'author', type: 'relationship', relationTo: 'users' }

// Rich text
{ name: 'content', type: 'richText' }

// Select
{ name: 'status', type: 'select', options: ['draft', 'published'] }

// Upload
{ name: 'image', type: 'upload', relationTo: 'media' }

// Array
{
  name: 'tags',
  type: 'array',
  fields: [{ name: 'tag', type: 'text' }],
}

// Blocks (polymorphic content)
{
  name: 'layout',
  type: 'blocks',
  blocks: [HeroBlock, ContentBlock, CTABlock],
}
ts
// 文本类型
{ name: 'title', type: 'text', required: true }

// 关联类型
{ name: 'author', type: 'relationship', relationTo: 'users' }

// 富文本类型
{ name: 'content', type: 'richText' }

// 选择类型
{ name: 'status', type: 'select', options: ['draft', 'published'] }

// 上传类型
{ name: 'image', type: 'upload', relationTo: 'media' }

// 数组类型
{
  name: 'tags',
  type: 'array',
  fields: [{ name: 'tag', type: 'text' }],
}

// 区块类型(多态内容)
{
  name: 'layout',
  type: 'blocks',
  blocks: [HeroBlock, ContentBlock, CTABlock],
}

Decision Framework

决策框架

When choosing between approaches:
ScenarioApproach
Data transformation before save
beforeChange
hook
Data transformation after read
afterRead
hook
Enforce business rulesAccess control function
Complex validation
validate
function on field
Computed display valueVirtual field with
afterRead
Related docs list
join
field type
Side effects (email, webhook)
afterChange
hook with context guard
Database-level constraintField with
unique: true
or
index: true
选择实现方式时参考:
场景实现方式
保存前的数据转换
beforeChange
钩子
读取后的数据转换
afterRead
钩子
执行业务规则访问控制函数
复杂验证字段上的
validate
函数
计算显示值搭配
afterRead
的虚拟字段
关联文档列表
join
字段类型
副作用操作(邮件、Webhook)带上下文守卫的
afterChange
钩子
数据库级约束设置
unique: true
index: true
的字段

Quality Checks

质量检查

Good Payload code:
  • All Local API calls with user context use
    overrideAccess: false
  • All hook operations pass
    req
    for transaction integrity
  • Recursive hooks use
    context
    flags
  • Types generated and imported from
    payload-types.ts
  • Access control functions are typed with
    Access
    type
  • Collections have meaningful
    admin.useAsTitle
    set
优质的 Payload 代码需满足:
  • 所有带用户上下文的本地 API 调用都使用
    overrideAccess: false
  • 所有钩子操作都传递
    req
    以保证事务完整性
  • 递归钩子使用
    context
    标记
  • payload-types.ts
    生成并导入类型
  • 访问控制函数使用
    Access
    类型定义
  • 集合设置了有意义的
    admin.useAsTitle

Reference Documentation

参考文档

For detailed patterns, see:
  • references/fields.md - All field types, validation, conditional logic
  • references/collections.md - Auth, uploads, drafts, live preview
  • references/hooks.md - Hook lifecycle, context, patterns
  • references/access-control.md - RBAC, row-level, field-level
  • references/queries.md - Operators, Local/REST/GraphQL APIs
  • references/advanced.md - Jobs, plugins, localization
如需详细模式,请查看:
  • references/fields.md - 所有字段类型、验证、条件逻辑
  • references/collections.md - 认证、上传、草稿、实时预览
  • references/hooks.md - 钩子生命周期、上下文、模式
  • references/access-control.md - 基于角色的访问控制、行级、字段级权限
  • references/queries.md - 操作符、本地/REST/GraphQL API
  • references/advanced.md - 作业、插件、本地化

Resources

资源