payload-cms
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePayload 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:
- Config Layer → Collections, globals, fields define your schema
- Hook Layer → Lifecycle events transform and validate data
- Access Layer → Functions control who can do what
Every operation flows through:
Config → Access Check → Hook Chain → Database → Response Hooks可以将 Payload 视为 三个相互关联的层级:
- 配置层 → Collections、全局配置、fields 定义你的数据模型
- 钩子层 → 生命周期事件用于转换和验证数据
- 访问层 → 函数控制用户操作权限
所有操作流程为:
配置 → 访问检查 → 钩子链 → 数据库 → 响应钩子Quick Reference
快速参考
| Task | Solution | Details |
|---|---|---|
| Auto-generate slugs | | [references/fields.md#slug-field] |
| Restrict by user | Access control with query constraint | [references/access-control.md] |
| Local API with auth | | [references/queries.md#local-api] |
| Draft/publish | | [references/collections.md#drafts] |
| Computed fields | | [references/fields.md#virtual] |
| Conditional fields | | [references/fields.md#conditional] |
| Filter relationships | | [references/fields.md#relationship] |
| Prevent hook loops | | [references/hooks.md#context] |
| Transactions | Pass | [references/hooks.md#transactions] |
| Background jobs | Jobs queue with tasks | [references/advanced.md#jobs] |
| 任务 | 解决方案 | 详情 |
|---|---|---|
| 自动生成别名 | | [references/fields.md#slug-field] |
| 按用户限制访问 | 带查询约束的访问控制 | [references/access-control.md] |
| 带认证的本地 API | | [references/queries.md#local-api] |
| 草稿/发布功能 | | [references/collections.md#drafts] |
| 计算字段 | | [references/fields.md#virtual] |
| 条件字段 | | [references/fields.md#conditional] |
| 过滤关联数据 | 字段上的 | [references/fields.md#relationship] |
| 避免钩子循环 | | [references/hooks.md#context] |
| 事务处理 | 为所有操作传递 | [references/hooks.md#transactions] |
| 后台任务 | 带任务的作业队列 | [references/advanced.md#jobs] |
Quick Start
快速开始
bash
npx create-payload-app@latest my-app
cd my-app
pnpm devbash
npx create-payload-app@latest my-app
cd my-app
pnpm devMinimal 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 for any operation acting on behalf of a user.
overrideAccess: false默认行为会绕过所有访问控制,这是最常见的安全漏洞。
ts
// ❌ 安全漏洞:即使传入 user 也会绕过访问控制
await payload.find({ collection: 'posts', user: someUser })
// ✅ 安全:显式强制执行权限
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // 必须设置
})规则:任何代表用户执行的操作都要使用 。
overrideAccess: false2. Transaction Integrity
2. 事务完整性
Operations without run in separate transactions.
reqts
// ❌ 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 to nested operations in hooks.
req未传入 的操作会在独立事务中运行。
reqts
// ❌ 数据损坏:独立事务
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, // 维持事务
})
}]
}规则:在钩子中执行嵌套操作时,始终传递 。
req3. 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.tssrc/
├── app/
│ ├── (frontend)/page.tsx
│ └── (payload)/admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/Header.ts
├── hooks/slugify.ts
└── payload.config.tsType 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:
| Scenario | Approach |
|---|---|
| Data transformation before save | |
| Data transformation after read | |
| Enforce business rules | Access control function |
| Complex validation | |
| Computed display value | Virtual field with |
| Related docs list | |
| Side effects (email, webhook) | |
| Database-level constraint | Field with |
选择实现方式时参考:
| 场景 | 实现方式 |
|---|---|
| 保存前的数据转换 | |
| 读取后的数据转换 | |
| 执行业务规则 | 访问控制函数 |
| 复杂验证 | 字段上的 |
| 计算显示值 | 搭配 |
| 关联文档列表 | |
| 副作用操作(邮件、Webhook) | 带上下文守卫的 |
| 数据库级约束 | 设置 |
Quality Checks
质量检查
Good Payload code:
- All Local API calls with user context use
overrideAccess: false - All hook operations pass for transaction integrity
req - Recursive hooks use flags
context - Types generated and imported from
payload-types.ts - Access control functions are typed with type
Access - Collections have meaningful set
admin.useAsTitle
优质的 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
资源
- Docs: https://payloadcms.com/docs
- LLM Context: https://payloadcms.com/llms-full.txt
- GitHub: https://github.com/payloadcms/payload
- Templates: https://github.com/payloadcms/payload/tree/main/templates