api-cms-payload

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Payload CMS Patterns

Payload CMS 模式

Quick Guide: Use Payload for code-first content management with TypeScript. Define collections and globals as config objects with typed fields, hooks, and access control functions. Prefer the Local API (
payload.find
,
payload.create
) for server-side operations. Always generate TypeScript types from your config. Use database adapters (Postgres or MongoDB) and never hardcode credentials. Access control functions receive
{ req }
with the authenticated user. Hooks run at the document lifecycle level (beforeChange, afterChange, etc.) and must not have side effects that block the request unless intentional.

<critical_requirements>
快速指南: 使用Payload进行基于TypeScript的代码优先内容管理。将集合和全局配置定义为包含类型化字段、钩子和访问控制函数的配置对象。服务器端操作优先使用Local API(
payload.find
、
payload.create
)。始终根据你的配置生成TypeScript类型。使用数据库适配器(Postgres或MongoDB),切勿硬编码凭据。访问控制函数会接收包含已认证用户的
{ req }
参数。钩子在文档生命周期层面运行(beforeChange、afterChange等),除非有意为之,否则不得包含会阻塞请求的副作用。

<critical_requirements>

CRITICAL: Before Using This Skill

重要提示:使用此技能前须知

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST define access control on every collection — open collections are a security risk)
(You MUST use the Local API (
payload.find
,
payload.create
) for server-side data operations — it is zero-latency and fully typed)
(You MUST generate TypeScript types with
payload generate:types
after every schema change)
(You MUST keep JSX/React component imports OUT of the Payload config file — separate config and UI concerns)
(You MUST use
overrideAccess: false
when calling the Local API on behalf of a user — the default is
true
which bypasses all access control)
</critical_requirements>

Auto-detection: Payload, payload, payloadcms, @payloadcms, buildConfig, CollectionConfig, GlobalConfig, payload.config.ts, payload.find, payload.create, payload.update, payload.delete, payload.findByID, lexicalEditor, richText, beforeChange, afterChange, afterRead, beforeValidate, access control payload, upload collection, imageSizes, versions drafts
When to use:
  • Configuring
    payload.config.ts
    with database adapter, collections, and globals
  • Defining collection schemas with typed fields (text, richText, relationship, blocks, array, group, upload, select)
  • Implementing access control functions (role-based, ownership-based, field-level)
  • Writing collection hooks (beforeChange, afterChange, beforeRead, afterRead, beforeValidate, beforeDelete, afterDelete)
  • Querying data via Local API, REST API, or GraphQL
  • Setting up authentication collections with login, roles, and JWT
  • Configuring uploads/media with image sizes and mime type restrictions
  • Enabling versions and drafts on collections or globals
  • Customizing the admin panel (groups, hidden collections, custom components)
Key patterns covered:
  • payload.config.ts
    setup with
    buildConfig
    , database adapters, editor config
  • Collection config: slug, fields, hooks, access, auth, upload, versions, admin
  • Field types: text, richText, relationship, upload, blocks, array, group, select, tabs, checkbox, date, number, email, code, json, point, radio, textarea, row, collapsible
  • Access control: collection-level and field-level, returning boolean or Where query
  • Hooks: beforeChange, afterChange, beforeRead, afterRead, beforeValidate, beforeDelete, afterDelete, beforeOperation, afterOperation
  • Local API:
    payload.find
    ,
    payload.findByID
    ,
    payload.create
    ,
    payload.update
    ,
    payload.delete
    ,
    payload.count
  • REST API: auto-generated endpoints at
    /api/{collection-slug}
  • Globals: singleton documents for site settings, navigation, footer
  • Auth collections:
    auth: true
    , roles, login strategies
  • Uploads: imageSizes, mimeTypes, media collections
  • Versions and drafts:
    versions: { drafts: true }
  • TypeScript type generation
When NOT to use:
  • Simple key-value storage (use a database directly)
  • Static site generation without content editing needs
  • Applications that only need a REST API without an admin panel (use a plain API framework)
  • Client-side data fetching patterns (Payload's Local API is server-only)
Detailed Resources:
  • For decision frameworks and anti-patterns, see reference.md
Core Setup & Collections:
  • examples/core.md — Config setup, collection definitions, field types, access control, hooks
Advanced Patterns:
  • examples/advanced.md — Globals, versions/drafts, uploads/media, auth collections, Local API, REST API

<philosophy>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入排序、
import type
、命名常量)
(你必须为每个集合定义访问控制——开放集合存在安全风险)
(服务器端数据操作必须使用Local API(
payload.find
、
payload.create
)——它具有零延迟且完全类型化)
(每次修改架构后,必须使用
payload generate:types
生成TypeScript类型)
(必须将JSX/React组件导入排除在Payload配置文件之外——分离配置与UI关注点)
(代表用户调用Local API时,必须设置
overrideAccess: false
——默认值为
true
,会绕过所有访问控制)
</critical_requirements>

自动检测关键词: Payload, payload, payloadcms, @payloadcms, buildConfig, CollectionConfig, GlobalConfig, payload.config.ts, payload.find, payload.create, payload.update, payload.delete, payload.findByID, lexicalEditor, richText, beforeChange, afterChange, afterRead, beforeValidate, access control payload, upload collection, imageSizes, versions drafts
适用场景:
  • 配置
    payload.config.ts
    ,包含数据库适配器、集合和全局配置
  • 定义带有类型化字段(text、richText、relationship、blocks、array、group、upload、select)的集合架构
  • 实现访问控制函数(基于角色、基于所有权、字段级)
  • 编写集合钩子(beforeChange、afterChange、beforeRead、afterRead、beforeValidate、beforeDelete、afterDelete)
  • 通过Local API、REST API或GraphQL查询数据
  • 设置包含登录、角色和JWT的认证集合
  • 配置带有图片尺寸和MIME类型限制的上传/媒体功能
  • 在集合或全局配置上启用版本和草稿功能
  • 自定义管理面板(分组、隐藏集合、自定义组件)
涵盖的核心模式:
  • 使用
    buildConfig
    、数据库适配器、编辑器配置搭建
    payload.config.ts
  • 集合配置:slug、字段、钩子、访问控制、认证、上传、版本、管理面板
  • 字段类型:text、richText、relationship、upload、blocks、array、group、select、tabs、checkbox、date、number、email、code、json、point、radio、textarea、row、collapsible
  • 访问控制:集合级和字段级,返回布尔值或Where查询
  • 钩子:beforeChange、afterChange、beforeRead、afterRead、beforeValidate、beforeDelete、afterDelete、beforeOperation、afterOperation
  • Local API:
    payload.find
    、
    payload.findByID
    、
    payload.create
    、
    payload.update
    、
    payload.delete
    、
    payload.count
  • REST API:自动生成的端点
    /api/{collection-slug}
  • 全局配置:用于站点设置、导航、页脚的单例文档
  • 认证集合:
    auth: true
    、角色、登录策略
  • 上传功能:imageSizes、mimeTypes、媒体集合
  • 版本和草稿:
    versions: { drafts: true }
  • TypeScript类型生成
不适用场景:
  • 简单键值存储(直接使用数据库)
  • 无需内容编辑的静态站点生成
  • 仅需要REST API而无需管理面板的应用(使用普通API框架)
  • 客户端数据获取模式(Payload的Local API仅支持服务器端)
详细资源:
  • 决策框架和反模式请参考reference.md
核心设置与集合:
  • examples/core.md — 配置搭建、集合定义、字段类型、访问控制、钩子
高级模式:
  • examples/advanced.md — 全局配置、版本/草稿、上传/媒体、认证集合、Local API、REST API

<philosophy>

Philosophy

设计理念

Payload is a TypeScript-native headless CMS that treats your schema as code. Instead of clicking through a GUI to build content models, you define collections and globals as TypeScript config objects. Payload auto-generates an admin panel, REST API, GraphQL API, and a fully typed Local API from your config.
Core principles:
  1. Config-as-code -- Collections, globals, fields, hooks, and access control are all defined in TypeScript. Your schema is version-controlled, reviewable, and deployable like any other code.
  2. Three APIs from one config -- Every collection automatically gets a Local API (server-only, zero-latency), REST API (
    /api/{slug}
    ), and GraphQL API. The Local API is the primary interface for server-side operations.
  3. Access control is mandatory -- Every collection should have explicit
    access
    functions. By default, Payload denies access to unauthenticated users, but you must define who can do what. Access functions can return a boolean or a
    Where
    query to scope results.
  4. Hooks for side effects -- Lifecycle hooks (beforeChange, afterChange, etc.) let you run logic at specific points in the document lifecycle. Keep hooks focused and avoid blocking operations unnecessarily.
  5. Database-agnostic -- Payload uses database adapters (Postgres or MongoDB). Your collections and fields are defined once and work with any supported database.
  6. Type generation -- Run
    payload generate:types
    to produce TypeScript interfaces from your config. This gives you end-to-end type safety from config to API responses.
When to use Payload:
  • Content-managed applications (blogs, e-commerce, marketing sites)
  • Applications needing an admin panel with role-based access
  • Projects requiring a typed CMS with version control over the schema
  • Multi-tenant applications using access control to scope data per tenant
  • Headless CMS backing a frontend framework
</philosophy>
<patterns>
Payload是原生支持TypeScript的无头CMS,将你的架构视为代码。无需通过GUI点击构建内容模型,你只需将集合和全局配置定义为TypeScript配置对象。Payload会根据你的配置自动生成管理面板、REST API、GraphQL API以及完全类型化的Local API。
核心原则:
  1. 配置即代码 —— 集合、全局配置、字段、钩子和访问控制均通过TypeScript定义。你的架构像其他代码一样支持版本控制、代码评审和部署。
  2. 一份配置生成三个API —— 每个集合自动拥有Local API(仅服务器端、零延迟)、REST API(
    /api/{slug}
    )和GraphQL API。Local API是服务器端操作的主要接口。
  3. 访问控制是强制性的 —— 每个集合都应有明确的
    access
    函数。默认情况下,Payload会拒绝未认证用户的访问,但你必须定义谁可以执行哪些操作。访问控制函数可以返回布尔值或
    Where
    查询来限定结果范围。
  4. 钩子处理副作用 —— 生命周期钩子(beforeChange、afterChange等)允许你在文档生命周期的特定阶段运行逻辑。保持钩子聚焦,避免不必要地阻塞操作。
  5. 数据库无关性 —— Payload使用数据库适配器(Postgres或MongoDB)。你的集合和字段只需定义一次,即可在所有支持的数据库上运行。
  6. 类型生成 —— 运行
    payload generate:types
    从你的配置生成TypeScript接口。这能为你提供从配置到API响应的端到端类型安全。
使用Payload的场景:
  • 内容管理应用(博客、电商、营销站点)
  • 需要基于角色访问控制的管理面板的应用
  • 需要类型化CMS且架构支持版本控制的项目
  • 使用访问控制为每个租户限定数据范围的多租户应用
  • 为前端框架提供支持的无头CMS
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: payload.config.ts Setup

模式1:payload.config.ts 配置

The config is the entry point. It defines the database adapter, collections, globals, editor, and admin settings. Always use env vars for credentials.
typescript
const config = buildConfig({
  db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL } }),
  editor: lexicalEditor(),
  collections: [Posts, Users, Media],
  globals: [SiteSettings],
  admin: { user: Users.slug },
  typescript: { outputFile: "./src/payload-types.ts" },
  secret: process.env.PAYLOAD_SECRET!,
});
Never hardcode database URLs or secrets. Import collections from separate files. See examples/core.md for full Postgres and MongoDB adapter configs.

配置文件是入口点,定义了数据库适配器、集合、全局配置、编辑器和管理面板设置。始终使用环境变量存储凭据。
typescript
const config = buildConfig({
  db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL } }),
  editor: lexicalEditor(),
  collections: [Posts, Users, Media],
  globals: [SiteSettings],
  admin: { user: Users.slug },
  typescript: { outputFile: "./src/payload-types.ts" },
  secret: process.env.PAYLOAD_SECRET!,
});
切勿硬编码数据库URL或密钥。从单独文件导入集合。完整的Postgres和MongoDB适配器配置请参考examples/core.md。

Pattern 2: Collection Config

模式2:集合配置

Collections are the primary data model. Each generates a database table, admin UI, and API endpoints. Define access control per operation, use
useAsTitle
for admin display, and keep each collection in its own file.
typescript
const Posts: CollectionConfig = {
  slug: "posts",
  admin: { useAsTitle: "title" },
  access: {
    read: () => true,
    create: ({ req: { user } }) => Boolean(user),
    update: isAdminOrAuthor,
    delete: isAdmin,
  },
  hooks: {
    beforeChange: [setAuthorOnCreate],
    afterChange: [revalidatePostCache],
  },
  versions: { drafts: true },
  fields: [
    { name: "title", type: "text", required: true },
    { name: "content", type: "richText" },
    {
      name: "author",
      type: "relationship",
      relationTo: "users",
      required: true,
    },
  ],
};
See examples/core.md for full collection config with all field types, sidebar positioning, and SEO tabs.

集合是主要的数据模型。每个集合会生成数据库表、管理UI和API端点。为每个操作定义访问控制,使用
useAsTitle
设置管理面板显示标题,并将每个集合放在单独文件中。
typescript
const Posts: CollectionConfig = {
  slug: "posts",
  admin: { useAsTitle: "title" },
  access: {
    read: () => true,
    create: ({ req: { user } }) => Boolean(user),
    update: isAdminOrAuthor,
    delete: isAdmin,
  },
  hooks: {
    beforeChange: [setAuthorOnCreate],
    afterChange: [revalidatePostCache],
  },
  versions: { drafts: true },
  fields: [
    { name: "title", type: "text", required: true },
    { name: "content", type: "richText" },
    {
      name: "author",
      type: "relationship",
      relationTo: "users",
      required: true,
    },
  ],
};
包含所有字段类型、侧边栏定位和SEO标签的完整集合配置请参考examples/core.md。

Pattern 3: Access Control

模式3:访问控制

Access functions receive
{ req }
with the authenticated user. They return
true
/
false
or a
Where
query to scope results. Define reusable functions in a shared
access/
directory.
typescript
// Return boolean for simple checks
const isAdmin: Access = ({ req: { user } }) => user?.role === "admin";

// Return Where query for scoped access — users see only their own documents
const isAdminOrSelf: Access = ({ req: { user } }) => {
  if (!user) return false;
  if (user.role === "admin") return true;
  return { author: { equals: user.id } };
};
Field-level access uses the same pattern on individual fields. See examples/core.md for reusable access functions and field-level access examples.

访问控制函数接收包含已认证用户的
{ req }
参数。返回
true
/
false
或
Where
查询来限定结果范围。在共享的
access/
目录中定义可复用函数。
typescript
// 简单检查返回布尔值
const isAdmin: Access = ({ req: { user } }) => user?.role === "admin";

// 返回Where查询实现范围访问——用户仅能查看自己的文档
const isAdminOrSelf: Access = ({ req: { user } }) => {
  if (!user) return false;
  if (user.role === "admin") return true;
  return { author: { equals: user.id } };
};
字段级访问在单个字段配置上使用相同模式。可复用访问函数和字段级访问示例请参考examples/core.md。

Pattern 4: Collection Hooks

模式4:集合钩子

Hooks run at specific points in the document lifecycle.
beforeChange
returns modified
data
,
afterChange
is for non-blocking side effects. Use
req.payload
to access the Local API within hooks. Hooks receive a
context
object to prevent infinite loops when hooks trigger other operations.
typescript
beforeChange: [
  ({ data, operation, req }) => {
    if (operation === "create" && req.user) data.author = req.user.id;
    return data;
  },
],
afterChange: [
  ({ doc, operation, req, context }) => {
    if (context.skipRevalidation) return;
    if (operation === "create") req.payload.logger.info(`Post created: ${doc.title}`);
  },
],
Never put blocking external API calls in
beforeChange
-- use
afterChange
for non-critical side effects. See examples/core.md for hook patterns and examples/advanced.md for cross-collection hooks.

钩子在文档生命周期的特定阶段运行。
beforeChange
返回修改后的
data
,
afterChange
用于非阻塞副作用。在钩子内使用
req.payload
访问Local API。钩子接收
context
对象以防止钩子触发其他操作时出现无限循环。
typescript
beforeChange: [
  ({ data, operation, req }) => {
    if (operation === "create" && req.user) data.author = req.user.id;
    return data;
  },
],
afterChange: [
  ({ doc, operation, req, context }) => {
    if (context.skipRevalidation) return;
    if (operation === "create") req.payload.logger.info(`Post created: ${doc.title}`);
  },
],
切勿在
beforeChange
中放入阻塞性的外部API调用——将非关键副作用放在
afterChange
中或使用后台处理。钩子模式请参考examples/core.md,跨集合钩子请参考examples/advanced.md。

Pattern 5: Field Types

模式5:字段类型

Payload provides typed fields:
text
,
richText
,
number
,
select
,
checkbox
,
date
,
email
,
textarea
,
relationship
,
upload
,
json
,
code
,
point
,
radio
. Structural fields compose to model any content shape:
  • group -- nested object with sub-fields
  • array -- repeatable rows of same-shape fields
  • blocks -- flexible content with multiple block types (use
    interfaceName
    for custom TypeScript interface names)
  • tabs, row, collapsible -- admin-only layout helpers that do not affect data shape
Use blocks when editors choose from multiple block types for flexible page layouts. Use array when every row has the same fields. See examples/core.md for block definitions and reference.md for the complete field type table.

Payload提供类型化字段:
text
、
richText
、
number
、
select
、
checkbox
、
date
、
email
、
textarea
、
relationship
、
upload
、
json
、
code
、
point
、
radio
。结构化字段可组合成任意内容形态:
  • group —— 包含子字段的嵌套对象
  • array —— 形状相同的可重复行
  • blocks —— 支持多种块类型的灵活内容(使用
    interfaceName
    自定义TypeScript接口名称)
  • tabs、row、collapsible —— 仅用于管理面板的布局辅助工具,不影响数据形态
当编辑器需要从多种块类型中选择以实现灵活页面布局时,使用blocks。当每行字段形状相同时,使用array。块定义请参考examples/core.md,完整字段类型表请参考reference.md。

Pattern 6: Local API

模式6:Local API

The Local API is the primary server-side interface -- zero-latency, fully typed, and executes hooks and access control. Always pass
overrideAccess: false
when operating on behalf of a user.
typescript
const payload = await getPayload({ config });
const result = await payload.find({
  collection: "posts",
  where: { status: { equals: "published" } },
  sort: "-createdAt",
  limit: 20,
  depth: 1,
  overrideAccess: false,
});
Without
overrideAccess: false
, access control is completely bypassed (the default is
true
). See examples/advanced.md for full CRUD operations, bulk updates, and globals API.
</patterns>
<decision_framework>
Local API是主要的服务器端接口——零延迟、完全类型化,并会执行钩子和访问控制。代表用户操作时,始终传递
overrideAccess: false
。
typescript
const payload = await getPayload({ config });
const result = await payload.find({
  collection: "posts",
  where: { status: { equals: "published" } },
  sort: "-createdAt",
  limit: 20,
  depth: 1,
  overrideAccess: false,
});
如果不设置
overrideAccess: false
,访问控制会被完全绕过(默认值为
true
)。完整的CRUD操作、批量更新和全局API请参考examples/advanced.md。
</patterns>
<decision_framework>

Decision Framework

决策框架

Which API to Use

选择哪种API

Where is the code running?
+-- Server-side (API route, server component, script)
|   +-- Local API (zero-latency, fully typed, preferred)
+-- External client (browser, mobile app, third-party)
|   +-- REST API (/api/{collection-slug})
+-- GraphQL client
    +-- GraphQL API (/api/graphql)
代码运行位置?
+-- 服务器端(API路由、服务器组件、脚本)
|   +-- Local API(零延迟、完全类型化、优先选择)
+-- 外部客户端(浏览器、移动应用、第三方)
|   +-- REST API(/api/{collection-slug})
+-- GraphQL客户端
    +-- GraphQL API(/api/graphql)

Field Type Selection

字段类型选择

What kind of data?
+-- Single value
|   +-- Short text --> text
|   +-- Long text --> textarea
|   +-- Rich content --> richText
|   +-- Number --> number
|   +-- Boolean --> checkbox
|   +-- Date/time --> date
|   +-- Email --> email
|   +-- Coordinates --> point
|   +-- Code snippet --> code
|   +-- Arbitrary JSON --> json
+-- Choice from options
|   +-- Single choice (dropdown) --> select
|   +-- Single choice (visible) --> radio
|   +-- Linked document --> relationship
|   +-- File/image --> upload
+-- Nested structure
|   +-- Fixed group of fields --> group
|   +-- Repeatable rows (same shape) --> array
|   +-- Flexible content (multiple block types) --> blocks
+-- Admin layout only (no data effect)
    +-- Tabbed sections --> tabs
    +-- Side-by-side fields --> row
    +-- Collapsible section --> collapsible
数据类型?
+-- 单个值
|   +-- 短文本 --> text
|   +-- 长文本 --> textarea
|   +-- 富内容 --> richText
|   +-- 数字 --> number
|   +-- 布尔值 --> checkbox
|   +-- 日期/时间 --> date
|   +-- 邮箱 --> email
|   +-- 坐标 --> point
|   +-- 代码片段 --> code
|   +-- 任意JSON --> json
+-- 选项选择
|   +-- 单选(下拉) --> select
|   +-- 单选(可见) --> radio
|   +-- 关联文档 --> relationship
|   +-- 文件/图片 --> upload
+-- 嵌套结构
|   +-- 固定字段组 --> group
|   +-- 可重复行(形状相同) --> array
|   +-- 灵活内容(多种块类型) --> blocks
+-- 仅管理面板布局(不影响数据)
    +-- 标签页区域 --> tabs
    +-- 并排字段 --> row
    +-- 可折叠区域 --> collapsible

Access Control Strategy

访问控制策略

Who should access this data?
+-- Public (anyone) --> read: () => true
+-- Authenticated users only --> read: ({ req: { user } }) => Boolean(user)
+-- Admin only --> read: ({ req: { user } }) => user?.role === 'admin'
+-- Owner only --> read: return Where query matching user.id
+-- Mixed (public read, auth write) --> Different function per operation
+-- Field-level restriction --> access on individual field config
谁可以访问这些数据?
+-- 公开(任何人) --> read: () => true
+-- 仅认证用户 --> read: ({ req: { user } }) => Boolean(user)
+-- 仅管理员 --> read: ({ req: { user } }) => user?.role === 'admin'
+-- 仅所有者 --> 返回匹配user.id的Where查询
+-- 混合模式(公开读取、认证写入) --> 为每个操作定义不同函数
+-- 字段级限制 --> 在单个字段配置上设置access

Hooks vs Access Control

钩子 vs 访问控制

What do you need to do?
+-- Control WHO can do something --> Access control
+-- Control WHAT happens when they do it --> Hooks
+-- Validate data before saving --> beforeValidate hook or field validation
+-- Transform data before saving --> beforeChange hook
+-- Trigger side effects after saving --> afterChange hook
+-- Filter/transform output --> afterRead hook
</decision_framework>

<red_flags>
你需要做什么?
+-- 控制谁可以执行操作 --> 访问控制
+-- 控制操作执行时发生什么 --> 钩子
+-- 保存前验证数据 --> beforeValidate钩子或字段验证
+-- 保存前转换数据 --> beforeChange钩子
+-- 保存后触发副作用 --> afterChange钩子
+-- 过滤/转换输出 --> afterRead钩子
</decision_framework>

<red_flags>

RED FLAGS

风险警示

High Priority Issues:
  • Missing access control on collections -- Without explicit
    access
    functions, Payload denies all access to unauthenticated users but grants full access to any authenticated user. Always define explicit access rules.
  • overrideAccess
    default is
    true
    in Local API
    -- Every
    payload.find()
    ,
    payload.create()
    , etc. call bypasses access control by default. Always pass
    overrideAccess: false
    when operating on behalf of a user.
  • Importing JSX/React components in payload.config.ts -- Payload config runs in a Node context. Importing React components (even transitively) causes bundling errors. Keep config and UI imports completely separate.
  • Hardcoded
    secret
    or database URL
    -- Use environment variables. The Payload secret is used to sign JWTs; hardcoding it is a security vulnerability.
Medium Priority Issues:
  • Using
    select("*")
    equivalent
    -- In the Local API, not specifying
    select
    returns all fields. Use the
    select
    option to fetch only needed fields for performance.
  • Deep
    depth
    values
    -- Default depth is 2. High depth values cause cascading relationship queries. Set
    depth: 0
    or
    depth: 1
    unless you need deeply nested relationships.
  • Blocking hooks with external calls --
    beforeChange
    and
    beforeValidate
    hooks block the save operation. Move non-critical external API calls to
    afterChange
    or use background processing.
  • Not running
    payload generate:types
    after schema changes
    -- Stale types lead to runtime errors that TypeScript should have caught at compile time.
Common Mistakes:
  • Deep-cloning collection configs --
    JSON.parse(JSON.stringify(config))
    strips hooks and access functions (they are functions, not serializable data). Use spread or Object.assign instead.
  • Forgetting
    .select()
    equivalent after create/update
    -- In the Local API,
    payload.create
    and
    payload.update
    return the full document by default. Use the
    select
    option if you need specific fields.
  • Using
    FOR ALL
    style access
    -- Define separate access functions for
    create
    ,
    read
    ,
    update
    ,
    delete
    instead of a single function. Different operations have different security requirements.
  • Monorepo version mismatches -- All packages in a monorepo must use the same version of
    payload
    ,
    @payloadcms/*
    ,
    next
    ,
    react
    , and
    react-dom
    . Mismatches cause subtle bundling errors.
Gotchas & Edge Cases:
  • beforeChange
    data is a partial on update
    -- On
    update
    operations,
    data
    contains only the changed fields, not the full document. Use
    originalDoc
    to access existing values.
  • beforeChange
    has no
    id
    on create
    -- The document ID is not available during
    beforeChange
    on create operations. If you need the ID, use
    afterChange
    .
  • overrideAccess
    defaults
    -- Local API defaults to
    true
    (bypass access control). REST and GraphQL always enforce access control. This asymmetry is intentional but catches people off guard.
  • Tabs, rows, and collapsibles do not affect data shape -- These are admin-only layout fields. A field inside a
    tab
    is stored at the top level of the document, not nested.
  • Relationship depth cascading -- Setting
    depth: 3
    on a collection with circular relationships can cause exponential query growth. Keep depth as low as possible.
  • Auth collections auto-inject fields -- Collections with
    auth: true
    automatically get
    email
    ,
    hash
    ,
    salt
    ,
    loginAttempts
    , and
    lockUntil
    fields. Do not redefine them.
  • Versions create a separate table -- Enabling
    versions: true
    creates a
    _posts_versions
    table (or equivalent). This can significantly increase storage for high-traffic collections.
  • Access control
    Where
    queries run as SQL
    -- When an access function returns a
    Where
    query instead of a boolean, it is appended to the database query. Complex
    Where
    queries can impact database performance.
</red_flags>

<critical_reminders>
高优先级问题:
  • 集合缺少访问控制 —— 没有明确的
    access
    函数时,Payload会拒绝未认证用户的所有访问,但会授予任何认证用户完全访问权限。始终定义明确的访问规则。
  • Local API中
    overrideAccess
    默认值为
    true
    —— 每次调用
    payload.find()
    、
    payload.create()
    等方法时,默认会绕过访问控制。代表用户操作时,始终传递
    overrideAccess: false
    。
  • 在payload.config.ts中导入JSX/React组件 —— Payload配置运行在Node环境中。导入React组件(即使是间接导入)会导致打包错误。完全分离配置与UI导入。
  • 硬编码
    secret
    或数据库URL
    —— 使用环境变量。Payload密钥用于签名JWT,硬编码会导致安全漏洞。
中优先级问题:
  • 使用等效于
    select("*")
    的操作
    —— 在Local API中,不指定
    select
    会返回所有字段。使用
    select
    选项仅获取所需字段以提升性能。
  • 过深的
    depth
    值
    —— 默认深度为2。过高的深度值会导致关联查询级联。除非需要深度嵌套关联,否则设置
    depth: 0
    或
    depth: 1
    。
  • 钩子中包含阻塞性外部调用 ——
    beforeChange
    和
    beforeValidate
    钩子会阻塞保存操作。将非关键外部API调用移至
    afterChange
    或使用后台处理。
  • 修改架构后未运行
    payload generate:types
    —— 过时的类型会导致TypeScript本应在编译时捕获的运行时错误。
常见错误:
  • 深度克隆集合配置 ——
    JSON.parse(JSON.stringify(config))
    会丢失钩子和访问控制函数(它们是函数,不可序列化)。使用扩展运算符或Object.assign替代。
  • 创建/更新后忘记等效的
    .select()
    操作
    —— 在Local API中,
    payload.create
    和
    payload.update
    默认返回完整文档。如果只需要特定字段,使用
    select
    选项。
  • 使用“全部允许”式访问控制 —— 为
    create
    、
    read
    、
    update
    、
    delete
    分别定义访问控制函数,而非单个函数。不同操作有不同的安全要求。
  • 单仓库版本不匹配 —— 单仓库中的所有包必须使用相同版本的
    payload
    、
    @payloadcms/*
    、
    next
    、
    react
    和
    react-dom
    。版本不匹配会导致难以察觉的打包错误。
陷阱与边缘情况:
  • 更新时
    beforeChange
    中的data是部分数据
    —— 在
    update
    操作中,
    data
    仅包含已更改的字段,而非完整文档。使用
    originalDoc
    访问现有值。
  • 创建时
    beforeChange
    中没有
    id
    —— 创建操作的
    beforeChange
    阶段无法获取文档ID。如果需要ID,使用
    afterChange
    。
  • overrideAccess
    默认值差异
    —— Local API默认值为
    true
    (绕过访问控制)。REST和GraphQL始终强制执行访问控制。这种不对称是有意设计的,但容易让人疏忽。
  • Tabs、rows和collapsibles不影响数据形态 —— 这些是仅用于管理面板的布局字段。
    tab
    内的字段存储在文档顶层,而非嵌套结构中。
  • 关联深度级联 —— 在存在循环关联的集合上设置
    depth: 3
    会导致查询呈指数级增长。尽可能保持深度值较低。
  • 认证集合自动注入字段 —— 设置
    auth: true
    的集合会自动获得
    email
    、
    hash
    、
    salt
    、
    loginAttempts
    和
    lockUntil
    字段。请勿重新定义这些字段。
  • 版本会创建单独的表 —— 启用
    versions: true
    会创建
    _posts_versions
    表(或等效表)。对于高流量集合,这会显著增加存储占用。
  • 访问控制的
    Where
    查询以SQL形式运行
    —— 当访问控制函数返回
    Where
    查询而非布尔值时,它会附加到数据库查询中。复杂的
    Where
    查询会影响数据库性能。
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST define access control on every collection — open collections are a security risk)
(You MUST use the Local API (
payload.find
,
payload.create
) for server-side data operations — it is zero-latency and fully typed)
(You MUST generate TypeScript types with
payload generate:types
after every schema change)
(You MUST keep JSX/React component imports OUT of the Payload config file — separate config and UI concerns)
(You MUST use
overrideAccess: false
when calling the Local API on behalf of a user — the default is
true
which bypasses all access control)
Failure to follow these rules will create security vulnerabilities, type-unsafe operations, and bundling errors.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入排序、
import type
、命名常量)
(你必须为每个集合定义访问控制——开放集合存在安全风险)
(服务器端数据操作必须使用Local API(
payload.find
、
payload.create
)——它具有零延迟且完全类型化)
(每次修改架构后,必须使用
payload generate:types
生成TypeScript类型)
(必须将JSX/React组件导入排除在Payload配置文件之外——分离配置与UI关注点)
(代表用户调用Local API时,必须设置
overrideAccess: false
——默认值为
true
,会绕过所有访问控制)
不遵守这些规则会导致安全漏洞、类型不安全操作和打包错误。
</critical_reminders>