payload
Payload CMSプロジェクト(payload.config.ts、コレクション、フィールド、フック、アクセス制御、Payload API)に関する作業時に使用します。バリデーションエラー、セキュリティ問題、リレーションシップクエリ、トランザクション、フックの動作のデバッグにも活用できます。
description の原文を見る
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.
SKILL.md 本文
Payload CMS アプリケーション開発
Payload は TypeScript ファーストアーキテクチャを備えた Next.js ネイティブ CMS で、管理パネル、データベース管理、REST/GraphQL API、認証、ファイルストレージを提供します。
クイックリファレンス
| タスク | ソリューション | 詳細 |
|---|---|---|
| スラッグの自動生成 | slugField() | FIELDS.md#slug-field-helper |
| ユーザーごとにコンテンツを制限 | クエリを使用したアクセス制御 | ACCESS-CONTROL.md#row-level-security-with-complex-queries |
| Local API ユーザー操作 | user + overrideAccess: false | QUERIES.md#access-control-in-local-api |
| ドラフト/公開ワークフロー | versions: { drafts: true } | COLLECTIONS.md#versioning--drafts |
| 計算フィールド | virtual: true とフィールドレベルの hooks.afterRead で値を返す | FIELDS.md#virtual-fields |
| 条件付きフィールド | admin.condition | FIELDS.md#conditional-fields |
| カスタムフィールド検証 | validate 関数 | FIELDS.md#validation |
| リレーションシップリストをフィルタ | フィールドの filterOptions | FIELDS.md#relationship |
| 特定のフィールドを選択 | select パラメータ | QUERIES.md#field-selection |
| 作成者/日付を自動設定 | beforeChange フック | HOOKS.md#collection-hooks |
| フックループを防止 | req.context チェック | HOOKS.md#context |
| カスケード削除 | beforeDelete フック | HOOKS.md#collection-hooks |
| 地理空間クエリ | point フィールドと near/within | FIELDS.md#point-geolocation |
| 逆向きリレーションシップ | join フィールドタイプ | FIELDS.md#join-fields |
| Next.js リバリデーション | afterChange でのコンテキスト制御 | HOOKS.md#nextjs-revalidation-with-context-control |
| リレーションシップでクエリ | ネストされたプロパティ構文 | QUERIES.md#nested-properties |
| 複雑なクエリ | AND/OR ロジック | QUERIES.md#andor-logic |
| トランザクション | 操作に req を渡す | ADAPTERS.md#threading-req-through-operations |
| バックグラウンドジョブ | ジョブキューとタスク | ADVANCED.md#jobs-queue |
| カスタム API ルート | コレクションカスタムエンドポイント | ADVANCED.md#custom-endpoints |
| クラウドストレージ | ストレージアダプタープラグイン | ADAPTERS.md#storage-adapters |
| 多言語対応 | localization 設定 + localized: true | ADVANCED.md#localization |
| プラグインを作成 | (options) => (config) => Config | 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 |
クイックスタート
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 を参照してください。
共通フィールド
// テキストフィールド
{ 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 を参照してください。
フックの例
フックは 2 つのレベルのいずれかに存在し、相互交換はできません。コレクションフックは { doc, data, req, operation, ... } を受け取り、ドキュメント全体に作用します。フィールドフックは個別フィールドの hooks オブジェクト内に存在し、{ value, siblingData, ... } を受け取り、そのフィールドの新しい値を返します。計算/仮想フィールド、フィールドごとのフォーマッター、フィールドごとのアクセスマスキングはフィールドフックです。フィールド間のビジネスロジックはコレクションフックです。
// コレクションレベル: ドキュメント全体のビジネスロジック
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' }],
}
// フィールドレベル: 単一フィールドの値を計算/フォーマット (仮想フィールドはこれを使用)
export const Users: CollectionConfig = {
slug: 'users',
fields: [
{ name: 'firstName', type: 'text' },
{ name: 'lastName', type: 'text' },
{
name: 'fullName',
type: 'text',
virtual: true,
hooks: {
afterRead: [({ siblingData }) => `${siblingData.firstName} ${siblingData.lastName}`],
},
},
],
}
「フィールドを計算する」または「フックでフィールドの値を入力する」と言われた場合は、そのフィールドのフィールドレベルフックを使用します。コレクションレベルの afterRead で doc を変更することはありません。
すべてのフックパターンについては HOOKS.md を参照してください。アクセス制御については ACCESS-CONTROL.md を参照してください。
タイプセーフなアクセス制御
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 },
}
}
クエリの例
// 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" } }
// depth がない場合、リレーションシップは ID のみを返します
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 0,
})
// 戻り値: { author: "user123" }
すべてのクエリ演算子および REST/GraphQL の例については、QUERIES.md を参照してください。
Payload インスタンスの取得
// 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>
}
セキュリティの落とし穴
1. Local API アクセス制御 (重大)
デフォルトでは、Local API 操作はすべてのアクセス制御をバイパスします。ユーザーを渡していても同じです。
// ❌ セキュリティバグ: ユーザーを渡していますが、権限は無視されます
await payload.find({
collection: 'posts',
user: someUser, // アクセス制御はバイパスされます!
})
// ✅ セキュア: ユーザーの権限を実際に強制します
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // アクセス制御に必須
})
各ケースの使い分け:
overrideAccess: true(デフォルト) - サーバー側の信頼できる操作 (cron ジョブ、システムタスク)overrideAccess: false- ユーザーに代わって操作する場合 (API ルート、ウェブフック)
QUERIES.md#access-control-in-local-api を参照してください。
2. フック内のトランザクション失敗
req なしでフック内のネストされた操作はトランザクションのアトミック性を破壊します。
// ❌ データ破損のリスク: 別のトランザクション
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. 無限フックループ
フックが同じフックをトリガーする操作をトリガーすると、無限ループが発生します。
// ❌ 無限ループ
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 を参照してください。
プロジェクト構造
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'),
},
// ...
})
// 使用法
import type { Post, User } from '@/payload-types'
よくある落とし穴
- Local API がアクセス制御をバイパス -
overrideAccess: falseを渡さない限り - ネストされた操作に
reqがない - トランザクションのアトミック性が破壊される - フックループ - フック内の操作は同じフックを再トリガーできます。
req.contextフラグを使用してください - フィールドレベルのアクセス - ブール値のみを返し、クエリ制約はありません
- リレーションシップの深さ - デフォルトは 2。ID のみの場合は
depth: 0を設定 - ドラフトステータス - ドラフトが有効な場合、
_statusフィールドは自動的に注入されます - タイプが古い -
generate:typesを実行するまで - MongoDB トランザクション - レプリカセット設定が必要です
- SQLite トランザクション - デフォルトで無効。
transactionOptions: {}で有効化 - ポイントフィールド - SQLite ではサポートされていません
ベストプラクティス
セキュリティ
- デフォルトは制限的、徐々に権限を追加
- Local API に
userを渡す場合はoverrideAccess: falseを使用 - フィールドレベルのアクセスはブール値のみを返します (クエリ制約はありません)
- クライアント提供のデータを信頼しない
saveToJWT: trueをロール用に使用してデータベース検索を回避
パフォーマンス
- 頻繁にクエリされるフィールドにインデックスを作成
selectを使用して返されるフィールドを制限- リレーションシップで
maxDepthを設定して過剰取得を防止 - アクセス制御での async 操作よりもクエリ制約を優先
req.contextで高コスト操作をキャッシュ
データ整合性
- フック内のネストされた操作には常に
reqを渡す - 無限フックループを防ぐためにコンテキストフラグを使用
- MongoDB (レプリカセット必須) と Postgres のトランザクションを有効化
- データフォーマット用に
beforeValidateを使用 - ビジネスロジック用に
beforeChangeを使用
タイプセーフティ
- スキーマ変更後に
generate:typesを実行 - 生成された
payload-types.tsからタイプをインポート - ユーザーオブジェクトを型指定:
import type { User } from '@/payload-types' - ランタイム型チェック用にフィールドタイプガードを使用
- 名前付き定数 (コレクション、フィールド、フック、アクセス関数、プラグインなど) に Payload タイプ (
CollectionConfig、Field、CollectionBeforeChangeHook、Access、Pluginなど) で注釈を付けるか、satisfies <Type>を使用してください。注釈がないと、type: 'text'のような文字列プロパティがstringに拡張され、判別ユニオン (Field、CollectionConfig) が解決に失敗します。インライン リテラルはコンテキスト型付けにより無料で取得されます。抽出された定数はそうではありません。
組織
- コレクションを別ファイルに保持
- アクセス制御を
access/ディレクトリに抽出 - フックを
hooks/ディレクトリに抽出 - 共通パターン用の再利用可能なフィールドファクトリを使用
- 複雑なアクセス制御をコメント付けで文書化
リファレンスドキュメント
FIELDS.md- すべてのフィールドタイプ、検証、管理オプションFIELD-TYPE-GUARDS.md- ランタイムフィールドタイプチェックと型の絞り込み用のタイプガードCOLLECTIONS.md- コレクション設定、認証、アップロード、ドラフト、ライブプレビューHOOKS.md- コレクションフック、フィールドフック、コンテキストパターンACCESS-CONTROL.md- コレクション、フィールド、グローバルアクセス制御、RBAC、マルチテナントACCESS-CONTROL-ADVANCED.md- コンテキスト認識、時間ベース、サブスクリプション ベースのアクセス、ファクトリ関数、テンプレートQUERIES.md- クエリ演算子、Local/REST/GraphQL APIENDPOINTS.md- カスタム API エンドポイント: 認証、ヘルパー、リクエスト/レスポンスパターンADAPTERS.md- データベース、ストレージ、メールアダプター、トランザクションADVANCED.md- 認証、ジョブ、エンドポイント、コンポーネント、プラグイン、ローカライゼーションPLUGIN-DEVELOPMENT.md- プラグインアーキテクチャ、モノレポ構造、パターン、ベストプラクティス
リソース
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- payloadcms
- リポジトリ
- payloadcms/payload
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/payloadcms/payload / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。