payload
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePayload Application Development
Payload 应用开发
Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.
Payload 是一款基于Next.js的原生CMS,采用TypeScript优先架构,提供管理面板、数据库管理、REST/GraphQL API、身份验证和文件存储功能。
Quick Reference
快速参考
| Task | Solution | Details |
|---|---|---|
| Auto-generate slugs | | FIELDS.md#slug-field-helper |
| Restrict content by user | Access control with query | ACCESS-CONTROL.md#row-level-security-with-complex-queries |
| Local API user ops | | QUERIES.md#access-control-in-local-api |
| Draft/publish workflow | | COLLECTIONS.md#versioning--drafts |
| Computed fields | | FIELDS.md#virtual-fields |
| Conditional fields | | FIELDS.md#conditional-fields |
| Custom field validation | | FIELDS.md#text-field |
| Filter relationship list | | FIELDS.md#relationship |
| Select specific fields | | QUERIES.md#local-api |
| Auto-set author/dates | beforeChange hook | HOOKS.md#collection-hooks |
| Prevent hook loops | | HOOKS.md#hook-context |
| Cascading deletes | beforeDelete hook | HOOKS.md#collection-hooks |
| Geospatial queries | | FIELDS.md#point-geolocation |
| Reverse relationships | | FIELDS.md#join-fields |
| Next.js revalidation | Context control in afterChange | HOOKS.md#nextjs-revalidation-with-context-control |
| Query by relationship | Nested property syntax | QUERIES.md#nested-properties |
| Complex queries | AND/OR logic | QUERIES.md#andor-logic |
| Transactions | Pass | ADAPTERS.md#threading-req-through-operations |
| Background jobs | Jobs queue with tasks | ADVANCED.md#jobs-queue |
| Custom API routes | Collection custom endpoints | ADVANCED.md#custom-endpoints |
| Cloud storage | Storage adapter plugins | ADAPTERS.md#storage-adapters |
| Multi-language | | ADVANCED.md#localization |
| Create plugin | | PLUGIN-DEVELOPMENT.md#plugin-architecture |
| Plugin package setup | Package structure with SWC | PLUGIN-DEVELOPMENT.md#plugin-package-structure |
| Add fields to collection | Map collections, spread fields | PLUGIN-DEVELOPMENT.md#adding-fields-to-collections |
| Plugin hooks | Preserve existing hooks in array | PLUGIN-DEVELOPMENT.md#adding-hooks |
| Check field type | Type guard functions | FIELD-TYPE-GUARDS.md |
| 任务 | 解决方案 | 详情 |
|---|---|---|
| 自动生成slug | | FIELDS.md#slug-field-helper |
| 按用户限制内容访问 | 带查询条件的访问控制 | ACCESS-CONTROL.md#row-level-security-with-complex-queries |
| Local API 用户操作 | | QUERIES.md#access-control-in-local-api |
| 草稿/发布工作流 | | COLLECTIONS.md#versioning--drafts |
| 计算字段 | 结合 | FIELDS.md#virtual-fields |
| 条件字段 | | FIELDS.md#conditional-fields |
| 自定义字段验证 | | FIELDS.md#text-field |
| 过滤关联列表 | 字段上的 | FIELDS.md#relationship |
| 选择特定字段 | | QUERIES.md#local-api |
| 自动设置作者/日期 | beforeChange 钩子 | HOOKS.md#collection-hooks |
| 防止钩子循环 | | HOOKS.md#hook-context |
| 级联删除 | beforeDelete 钩子 | HOOKS.md#collection-hooks |
| 地理空间查询 | | FIELDS.md#point-geolocation |
| 反向关联 | | FIELDS.md#join-fields |
| Next.js 重新验证 | afterChange钩子中的上下文控制 | HOOKS.md#nextjs-revalidation-with-context-control |
| 按关联关系查询 | 嵌套属性语法 | QUERIES.md#nested-properties |
| 复杂查询 | AND/OR 逻辑 | QUERIES.md#andor-logic |
| 事务处理 | 向操作传递 | ADAPTERS.md#threading-req-through-operations |
| 后台任务 | 带任务的作业队列 | ADVANCED.md#jobs-queue |
| 自定义API路由 | 集合自定义端点 | ADVANCED.md#custom-endpoints |
| 云存储 | 存储适配器插件 | ADAPTERS.md#storage-adapters |
| 多语言支持 | | ADVANCED.md#localization |
| 创建插件 | | PLUGIN-DEVELOPMENT.md#plugin-architecture |
| 插件包设置 | 基于SWC的包结构 | PLUGIN-DEVELOPMENT.md#plugin-package-structure |
| 向集合添加字段 | 映射集合并展开字段 | PLUGIN-DEVELOPMENT.md#adding-fields-to-collections |
| 插件钩子 | 保留数组中的现有钩子 | PLUGIN-DEVELOPMENT.md#adding-hooks |
| 检查字段类型 | 类型守卫函数 | FIELD-TYPE-GUARDS.md |
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'
import path from 'path'
import { fileURLToPath } from 'url'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
admin: {
user: 'users',
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Media],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET,
typescript: {
outputFile: path.resolve(dirname, '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'
import path from 'path'
import { fileURLToPath } from 'url'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
admin: {
user: 'users',
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Media],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET,
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: mongooseAdapter({
url: process.env.DATABASE_URL,
}),
})Essential Patterns
核心模式
Basic Collection
基础集合
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' },
],
timestamps: true,
}For more collection patterns (auth, upload, drafts, live preview), see COLLECTIONS.md.
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' },
],
timestamps: true,
}更多集合模式(身份验证、上传、草稿、实时预览)请查看 COLLECTIONS.md。
Common Fields
常用字段
ts
// Text field
{ name: 'title', type: 'text', required: true }
// Relationship
{ name: 'author', type: 'relationship', relationTo: 'users', required: true }
// Rich text
{ name: 'content', type: 'richText', required: true }
// Select
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }
// Upload
{ name: 'image', type: 'upload', relationTo: 'media' }For all field types (array, blocks, point, join, virtual, conditional, etc.), see FIELDS.md.
ts
// 文本字段
{ name: 'title', type: 'text', required: true }
// 关联字段
{ name: 'author', type: 'relationship', relationTo: 'users', required: true }
// 富文本字段
{ name: 'content', type: 'richText', required: true }
// 选择字段
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }
// 上传字段
{ name: 'image', type: 'upload', relationTo: 'media' }所有字段类型(数组、块、点、关联、虚拟、条件等)请查看 FIELDS.md。
Hook Example
钩子示例
ts
export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create') {
data.slug = slugify(data.title)
}
return data
},
],
},
fields: [{ name: 'title', type: 'text' }],
}For all hook patterns, see HOOKS.md. For access control, see ACCESS-CONTROL.md.
ts
export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create') {
data.slug = slugify(data.title)
}
return data
},
],
},
fields: [{ name: 'title', type: 'text' }],
}所有钩子模式请查看 HOOKS.md。访问控制相关内容请查看 ACCESS-CONTROL.md。
Access Control with Type Safety
类型安全的访问控制
ts
import type { Access } from 'payload'
import type { User } from '@/payload-types'
// Type-safe access control
export const adminOnly: Access = ({ req }) => {
const user = req.user as User
return user?.roles?.includes('admin') || false
}
// Row-level access control
export const ownPostsOnly: Access = ({ req }) => {
const user = req.user as User
if (!user) return false
if (user.roles?.includes('admin')) return true
return {
author: { equals: user.id },
}
}ts
import type { Access } from 'payload'
import type { User } from '@/payload-types'
// 类型安全的访问控制
export const adminOnly: Access = ({ req }) => {
const user = req.user as User
return user?.roles?.includes('admin') || false
}
// 行级访问控制
export const ownPostsOnly: Access = ({ req }) => {
const user = req.user as User
if (!user) return false
if (user.roles?.includes('admin')) return true
return {
author: { equals: user.id },
}
}Query Example
查询示例
ts
// Local API
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
'author.name': { contains: 'john' },
},
depth: 2,
limit: 10,
sort: '-createdAt',
})
// Query with populated relationships
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 2, // Populates relationships (default is 2)
})
// Returns: { author: { id: "user123", name: "John" } }
// Without depth, relationships return IDs only
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 0,
})
// Returns: { author: "user123" }For all query operators and REST/GraphQL examples, see QUERIES.md.
ts
// Local API
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
'author.name': { contains: 'john' },
},
depth: 2,
limit: 10,
sort: '-createdAt',
})
// 带关联数据填充的查询
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 2, // 填充关联数据(默认值为2)
})
// 返回结果: { author: { id: "user123", name: "John" } }
// 不填充关联数据,仅返回ID
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 0,
})
// 返回结果: { author: "user123" }所有查询操作符及REST/GraphQL示例请查看 QUERIES.md。
Getting Payload Instance
获取Payload实例
ts
// In API routes (Next.js)
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
import { getPayload } from 'payload'
import config from '@payload-config'
export default async function Page() {
const payload = await getPayload({ config })
const { docs } = await payload.find({ collection: 'posts' })
return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
}ts
// 在API路由中(Next.js)
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)
}
// 在Server Components中
import { getPayload } from 'payload'
import config from '@payload-config'
export default async function Page() {
const payload = await getPayload({ config })
const { docs } = await payload.find({ collection: 'posts' })
return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
}Logger Usage
日志器使用
ts
// ✅ Valid: single string
payload.logger.error('Something went wrong')
// ✅ Valid: object with msg and err
payload.logger.error({ msg: 'Failed to process', err: error })
// ❌ Invalid: don't pass error as second argument
payload.logger.error('Failed to process', error)
// ❌ Invalid: use `err` not `error`, use `msg` not `message`
payload.logger.error({ message: 'Failed', error: error })ts
// ✅ 正确用法:单个字符串
payload.logger.error('Something went wrong')
// ✅ 正确用法:包含msg和err的对象
payload.logger.error({ msg: 'Failed to process', err: error })
// ❌ 错误用法:不要将错误作为第二个参数传递
payload.logger.error('Failed to process', error)
// ❌ 错误用法:使用`err`而非`error`,使用`msg`而非`message`
payload.logger.error({ message: 'Failed', error: error })Security Pitfalls
安全陷阱
1. Local API Access Control (CRITICAL)
1. Local API 访问控制(关键)
By default, Local API operations bypass ALL access control, even when passing a user.
ts
// ❌ SECURITY BUG: Passes user but ignores their permissions
await payload.find({
collection: 'posts',
user: someUser, // Access control is BYPASSED!
})
// ✅ SECURE: Actually enforces the user's permissions
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // REQUIRED for access control
})When to use each:
- (default) - Server-side operations you trust (cron jobs, system tasks)
overrideAccess: true - - When operating on behalf of a user (API routes, webhooks)
overrideAccess: false
See QUERIES.md#access-control-in-local-api.
默认情况下,Local API操作会绕过所有访问控制,即使传递了用户信息。
ts
// ❌ 安全漏洞:传递了用户但忽略其权限
await payload.find({
collection: 'posts',
user: someUser, // 访问控制被绕过!
})
// ✅ 安全写法:实际强制执行用户权限
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // 启用访问控制必须设置此参数
})使用场景:
- (默认值)- 可信的服务器端操作(定时任务、系统任务)
overrideAccess: true - - 代表用户执行操作时(API路由、Webhook)
overrideAccess: false
详情请查看 QUERIES.md#access-control-in-local-api。
2. Transaction Failures in Hooks
2. 钩子中的事务失败
Nested operations in hooks without break transaction atomicity.
reqts
// ❌ DATA CORRUPTION RISK: Separate transaction
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
// Missing req - runs in separate transaction!
})
},
]
}
// ✅ ATOMIC: Same transaction
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
req, // Maintains atomicity
})
},
]
}See ADAPTERS.md#threading-req-through-operations.
钩子中未传递的嵌套操作会破坏事务原子性。
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, // 保持原子性
})
},
]
}详情请查看 ADAPTERS.md#threading-req-through-operations。
3. Infinite Hook Loops
3. 无限钩子循环
Hooks triggering operations that trigger the same hooks 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: Use context flag
hooks: {
afterChange: [
async ({ doc, req, context }) => {
if (context.skipHooks) return
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
context: { skipHooks: true },
req,
})
},
]
}See HOOKS.md#context.
钩子触发的操作再次触发相同钩子会导致无限循环。
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.skipHooks) return
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
context: { skipHooks: true },
req,
})
},
]
}详情请查看 HOOKS.md#context。
Project Structure
项目结构
txt
src/
├── app/
│ ├── (frontend)/
│ │ └── page.tsx
│ └── (payload)/
│ └── admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/
│ └── Header.ts
├── components/
│ └── CustomField.tsx
├── hooks/
│ └── slugify.ts
└── payload.config.tstxt
src/
├── app/
│ ├── (frontend)/
│ │ └── page.tsx
│ └── (payload)/
│ └── admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/
│ └── Header.ts
├── components/
│ └── CustomField.tsx
├── hooks/
│ └── slugify.ts
└── payload.config.tsType Generation
类型生成
ts
// payload.config.ts
export default buildConfig({
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
// ...
})
// Usage
import type { Post, User } from '@/payload-types'ts
// payload.config.ts
export default buildConfig({
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
// ...
})
// 使用示例
import 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
- FIELDS.md - 所有字段类型、验证、管理端选项
- FIELD-TYPE-GUARDS.md - 用于运行时字段类型检查和收窄的类型守卫
- COLLECTIONS.md - 集合配置、身份验证、上传、草稿、实时预览
- HOOKS.md - 集合钩子、字段钩子、上下文模式
- ACCESS-CONTROL.md - 集合、字段、全局访问控制、RBAC、多租户
- ACCESS-CONTROL-ADVANCED.md - 上下文感知、基于时间、基于订阅的访问控制、工厂函数、模板
- QUERIES.md - 查询操作符、Local/REST/GraphQL API
- ENDPOINTS.md - 自定义API端点:身份验证、助手函数、请求/响应模式
- ADAPTERS.md - 数据库、存储、邮件适配器、事务
- ADVANCED.md - 身份验证、作业、端点、组件、插件、多语言
- PLUGIN-DEVELOPMENT.md - 插件架构、单仓库结构、模式、最佳实践
Resources
资源
- llms-full.txt: https://payloadcms.com/llms-full.txt
- Docs: https://payloadcms.com/docs
- GitHub: https://github.com/payloadcms/payload
- Examples: https://github.com/payloadcms/payload/tree/main/examples
- Templates: https://github.com/payloadcms/payload/tree/main/templates