关于 payload-cms
payload-cms 是为 Payload CMS 开发提供指导的技能,面向使用 Next.js 和 TypeScript 构建 CMS 的开发者。它重点涵盖集合配置、钩子、访问控制、查询及安全实践。
主要功能
- 01集合、字段与全局配置模式
- 02生命周期钩子与数据处理
- 03访问控制和用户权限
- 04Local API 查询与关系字段
- 05事务完整性和钩子循环防护
适用场景
- 01为内容集合配置字段与草稿发布
- 02按用户权限限制数据访问
- 03在钩子中处理数据并保持事务一致
- 04诊断 Local API 的访问控制问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ connorads/ dotfiles --skill payload-cms安装后在 Claude Code 中输入/payload-cms确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdREADME.mdreferences/access-control.mdadvanced.mdcollections.mdfields.mdhooks.mdqueries.md详细介绍
Payload CMS 开发
Payload 是一款原生支持 Next.js 的 CMS,采用 TypeScript 优先的架构。本技能传授构建集合、钩子、访问控制和查询的专业知识。
心智模型
将 Payload 视为三个相互关联的层:
- 配置层 → 集合、全局配置和字段定义你的架构
- 钩子层 → 生命周期事件用于转换和验证数据
- 访问层 → 函数控制谁可以执行哪些操作
每项操作的流程为:Config → Access Check → Hook Chain → Database → Response Hooks
快速参考
| 任务 | 解决方案 | 详情 |
|---|---|---|
| 自动生成 slug | slugField() 或 beforeChange 钩子 | [references/fields.md#slug-field] |
| 按用户限制访问 | 使用查询约束的访问控制 | [references/access-control.md] |
| 带身份验证的 Local API | user + overrideAccess: false | [references/queries.md#local-api] |
| 草稿/发布 | versions: { drafts: true } | [references/collections.md#drafts] |
| 计算字段 | 使用 afterRead 钩子的 virtual: true | [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] |
快速开始
npx create-payload-app@latest my-app
cd my-app
pnpm dev
最小配置
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 }),
})
核心模式
集合定义
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,
}
钩子模式(自动生成 slug)
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 }],
}
访问控制模式
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 } }
}
查询模式
// 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
})
关键安全规则
1. Local API 访问控制
默认行为会绕过所有访问控制。 这是最常见的安全错误。
// ❌ 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
})
规则: 任何代表用户执行的操作都应使用 overrideAccess: false。
2. 事务完整性
不带 req 的操作会在独立事务中运行。
// ❌ 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
})
}]
}
规则: 在钩子中的嵌套操作里始终传递 req。
3. 无限钩子循环
触发自身的钩子会导致无限循环。
// ❌ 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 },
})
}]
}
项目结构
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
类型生成
架构变更后生成类型:
// payload.config.ts
export default buildConfig({
typescript: { outputFile: 'payload-types.ts' },
})
// Usage
import type { Post, User } from '@/payload-types'
获取 Payload 实例
// 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>
}
常见字段类型
// 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],
}
决策框架
选择方案时:
| 场景 | 方案 |
|---|---|
| 保存前转换数据 | beforeChange 钩子 |
| 读取后转换数据 | afterRead 钩子 |
| 执行业务规则 | 访问控制函数 |
| 复杂验证 | 字段上的 validate 函数 |
| 计算显示值 | 带 afterRead 的虚拟字段 |
| 关联文档列表 | join 字段类型 |
| 副作用(电子邮件、webhook) | 带上下文防护的 afterChange 钩子 |
| 数据库级约束 | 带 unique: true 或 index: true 的字段 |
质量检查
优质的 Payload 代码:
- 所有带用户上下文的 Local API 调用都使用
overrideAccess: false - 所有钩子操作都传递
req,以确保事务完整性 - 递归钩子使用
context标记 - 已生成类型,并从
payload-types.ts导入 - 访问控制函数使用
Access类型进行类型标注 - 集合设置了有意义的
admin.useAsTitle
参考文档
如需了解详细模式,请参阅:
- references/fields.md - 所有字段类型、验证和条件逻辑
- references/collections.md - 身份验证、上传、草稿和实时预览
- references/hooks.md - 钩子生命周期、上下文和模式
- references/access-control.md - RBAC、行级和字段级控制
- references/queries.md - 运算符、Local/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
评论
支持 Markdown