payload について
Next.js と TypeScript で Payload CMS アプリケーションを開発する AI コーディングエージェント向けのリファレンススキルです。コレクション、フィールド、アクセス制御、クエリ、フック、プラグインの実装パターンをまとめています。
主な機能
- 01コレクションとフィールドの設定パターン
- 02アクセス制御とクエリのガイド
- 03フック、トランザクション、ジョブキュー
- 04REST と GraphQL API のリファレンス
- 05プラグイン開発ガイド
活用シーン
- 01Payload CMS アプリケーションの構築や拡張
- 02アクセスルールとデータクエリの設定
- 03Payload プラグインやカスタムエンドポイントの開発
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ noklip-io/ agent-skills --skill payloadインストール後、Claude Code で/payloadを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreference/ACCESS-CONTROL-ADVANCED.mdACCESS-CONTROL.mdADAPTERS.mdADVANCED.mdCOLLECTIONS.mdENDPOINTS.mdFIELD-TYPE-GUARDS.mdFIELDS.mdHOOKS.mdPLUGIN-DEVELOPMENT.mdQUERIES.md詳細
Payload CMS アプリケーション開発
Payload は Next.js ネイティブの CMS です。TypeScript を中心としたアーキテクチャを採用し、管理パネル、データベース管理、REST/GraphQL API、認証、ファイルストレージを提供します。
インストールに関する注意
これは、AI コーディングエージェント向けの Payload スキルの単独バージョンです。
Claude Code ユーザー向け:
- このスキルに加え、MCP ツールやその他の機能を含む公式の Payload Marketplace プラグインを優先してインストールしてください
- フルプラグインを使用しない場合にのみ、この単独スキルをインストールしてください
その他の AI エージェント(Cursor、OpenCode、GitHub Copilot など)向け:
- このスキルを直接インストールしてください:
npx skills add noklip-io/agent-skills --skill payload
クイックリファレンス
クイックスタート
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'
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,
}),
})
基本的なパターン
基本的なコレクション
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 を参照してください。
よく使うフィールド
// 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' }
すべてのフィールドタイプ(array、blocks、point、join、virtual、conditional など)については、FIELDS.md を参照してください。
フックの例
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 を参照してください。
型安全なアクセス制御
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 },
}
}
クエリの例
// 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" }
すべてのクエリオペレーターと REST/GraphQL の例については、QUERIES.md を参照してください。
Payload インスタンスの取得
// 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>
}
セキュリティ上の注意点
1. Local API のアクセス制御(重大)
デフォルトでは、Local API の操作はすべてのアクセス制御をバイパスします。ユーザーを渡した場合も同様です。
// ❌ 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
})
使い分け:
overrideAccess: true(デフォルト)- 信頼できるサーバーサイドの操作(cron ジョブ、システムタスク)overrideAccess: false- ユーザーに代わって操作する場合(API ルート、Webhook)
QUERIES.md#access-control-in-local-api を参照してください。
2. フックでのトランザクション失敗
req を使わずにフック内でネストした操作を行うと、トランザクションの原子性が損なわれます。
// ❌ 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
})
},
]
}
ADAPTERS.md#threading-req-through-operations を参照してください。
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: 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,
})
},
]
}
HOOKS.md#context を参照してください。
プロジェクト構成
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.ts
型の生成
// payload.config.ts
export default buildConfig({
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
// ...
})
// Usage
import type { Post, User } from '@/payload-types'
リファレンスドキュメント
- 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 - プラグインアーキテクチャ、モノレポ構成、パターン、ベストプラクティス