mcpField
P
payload

payload

github.com/payloadcms開発45.6k2.7k 回インストール今日更新
リポジトリ

payload について

Payload アプリケーション開発向けのスキルです。Next.js と TypeScript のプロジェクトで使う CMS 設定、クエリ、フック、アクセス制御、プラグインのパターンを扱います。

主な機能

  1. 01コレクションとフィールドの設計
  2. 02下書き、バージョン、スラッグ
  3. 03クエリとアクセス制御
  4. 04フックとトランザクションの指針
  5. 05プラグインと API のパターン

活用シーン

  1. 01コンテンツコレクションの設定
  2. 02ロール別コンテンツアクセスの実装
  3. 03フックとバックグラウンドジョブの追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/payloadcms/payload --skill payload

インストール後、Claude Code で/payloadを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

reference/
ACCESS-CONTROL-ADVANCED.md
ACCESS-CONTROL.md
ADAPTERS.md
ADVANCED.md
COLLECTIONS.md
ENDPOINTS.md
FIELD-TYPE-GUARDS.md
FIELDS.md
HOOKS.md
PLUGIN-DEVELOPMENT.md
QUERIES.md

詳細

Payload アプリケーション開発

Payload は Next.js ネイティブの CMS で、TypeScript ファーストのアーキテクチャを採用しており、管理パネル、データベース管理、REST/GraphQL API、認証、ファイルストレージを提供します。

クイックリファレンス

タスク解決策詳細
スラッグの自動生成{ type: 'slug', useAsSlug: 'title' }FIELDS.md#slug-field
ユーザーによるコンテンツの制限クエリによるアクセス制御ACCESS-CONTROL.md#row-level-security-with-complex-queries
ローカル API のユーザー操作user + overrideAccess: falseQUERIES.md#access-control-in-local-api
下書き/公開ワークフローversions: { drafts: true }COLLECTIONS.md#versioning--drafts
計算フィールド値を返すフィールドレベルの hooks.afterRead を伴う virtual: trueFIELDS.md#virtual-fields
ドキュメントのタイトルadmin.useAsTitle に保存されたトップレベルのフィールドCOLLECTIONS.md#useastitle
条件付きフィールドadmin.conditionFIELDS.md#conditional-fields
カスタムフィールドのバリデーションvalidate 関数FIELDS.md#validation
リレーションシップリストのフィルタリングフィールドの filterOptionsFIELDS.md#relationship
特定のフィールドの選択select パラメータQUERIES.md#field-selection
作成者/日付の自動設定beforeChange フックHOOKS.md#collection-hooks
フックのループの防止req.context チェックHOOKS.md#context
カスケード削除beforeDelete フックHOOKS.md#collection-hooks
地理空間クエリnear/within を伴う point フィールド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: trueADVANCED.md#localization
プラグインの作成(options) => (config) => ConfigPLUGIN-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,
  }),
})

必須のパターン

デフォルトと規約

明確な理由がない限り、コンテンツをモデリングする際は以下のデフォルトを適用してください:

  • デフォルトで下書き/バージョンを有効化: versions: { drafts: true }。これはあらゆる コンテンツコレクションに推奨される出発点です。_status フィールド (draft / published / changed)を自動的に注入します — 独自の status フィールドを追加しないでください、冗長です。バージョンをスキップするのは、 公開/下書きのライフサイクルを持たないコレクション(例:内部の結合テーブル、設定)だけにしてください。
  • すべてのスラッグにはネイティブの slug フィールド型を使用する。手書きの { name: 'slug', type: 'text', unique: true } は使わないでください。これはソースフィールドから スラッグを自動生成し、再生成トグルを追加し、デフォルトで required、unique、 index、position: 'sidebar' になります。useAsSlug は必須です — 生成元の ソースフィールドを指定してください:{ name: 'slug', type: 'slug', useAsSlug: 'title' }。
  • position: 'sidebar' は短く一目でわかるフィールド向け — ステータス、カテゴリ、 著者、公開日など。使うのに横幅が必要な長いフィールド(説明、リッチテキスト、 長文テキスト)には避けてください。それらはメインドキュメント領域に属します。
  • admin.useAsTitle には保存されるトップレベルフィールドを使用する。 virtual: true で 設定された計算フィールドを admin.useAsTitle に決して設定しないでください。これらの フィールドはクエリ不可であり、Payload はその設定を拒否します。virtual: 'author.name' のような リレーションシップパスの仮想フィールドはサポートされていますが、タイトルが関連ドキュメントから 取得される必要がある場合にのみ使用してください。
基本的なコレクション
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
    // _status (from versions.drafts) shows the draft/published state — no custom status field needed
    defaultColumns: ['title', 'author', '_status', 'createdAt'],
  },
  versions: {
    drafts: true,
  },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'slug', useAsSlug: 'title' }, // auto-generates from `title`, unique + indexed, sidebar position
    { name: 'content', type: 'richText' }, // long field — stays in the main area, not the sidebar
    // short, at-a-glance field — good sidebar candidate
    { name: 'author', type: 'relationship', relationTo: 'users', admin: { position: 'sidebar' } },
  ],
  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 }

// Slug — use the native field type instead of a hand-rolled text field
{ name: 'slug', type: 'slug', useAsSlug: 'title' }

// Select (for genuine taxonomy — NOT publish state; use versions.drafts + _status for that)
{ name: 'category', type: 'select', options: ['news', 'tutorial', 'opinion'] }

// Upload
{ name: 'image', type: 'upload', relationTo: 'media' }

すべてのフィールド型(array、blocks、point、join、virtual、conditional など)については、FIELDS.md を参照してください。

フックの例

フックは2つのレベルのいずれかに存在し、それらは互換性がありません。コレクションフックは { doc, data, req, operation, ... } を受け取り、ドキュメント全体に対して作用します。フィールドフックは 個々のフィールドの hooks オブジェクト内に存在し、{ value, siblingData, ... } を受け取り、 そのフィールドの新しい値を返します。計算/仮想フィールド、フィールド単位のフォーマッター、 フィールド単位のアクセスマスキングはフィールドフックです。フィールドをまたぐビジネスロジックは コレクションフックです。

// Collection-level: business logic across the document
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' }],
}

// Field-level: compute / format a single field's value (virtual fields use this)
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}`],
      },
    },
  ],
}

「フィールドを計算する」または「フックでフィールドの値を設定する」よう求められた場合は、 そのフィールドにフィールドレベルのフックを使用してください — doc を変更する コレクションレベルの afterRead は決して使用しないでください。

すべてのフックパターンについては、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',
  overrideAccess: true,
  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',
  overrideAccess: true,
  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',
  overrideAccess: true,
  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(request: Request) {
  const payload = await getPayload({ config })
  const { user } = await payload.auth({ headers: request.headers })

  const posts = await payload.find({
    collection: 'posts',
    overrideAccess: false, // this route answers for whoever called it
    user,
  })

  return Response.json(posts)
}

// In Server Components
import { headers } from 'next/headers'
import { getPayload } from 'payload'
import config from '@payload-config'

export default async function Page() {
  const payload = await getPayload({ config })
  const { user } = await payload.auth({ headers: await headers() })
  const { docs } = await payload.find({
    collection: 'posts',
    overrideAccess: false,
    user,
  })

  return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
}

セキュリティ上の落とし穴

1. Local API のアクセス制御(重大)

overrideAccess: true はすべてのアクセス制御をバイパスします。ユーザーが渡された場合でも同様です。

// ❌ SECURITY BUG: Passes user but bypasses their permissions anyway
await payload.find({
  collection: 'posts',
  user: someUser,
  overrideAccess: true, // Access control is BYPASSED!
})

// ✅ SECURE: Respects the user's permissions — this is the default, overrideAccess can be omitted
await payload.find({
  collection: 'posts',
  user: someUser,
})

overrideAccess が省略可能な Local API 操作では、デフォルトは false です — 明示的に バイパスしない限り、アクセス制御は尊重されます。

  • 省略する、または overrideAccess: false を設定する - ユーザーに代わって操作する場合(API ルート、ユーザー向けのサーバー関数、ユーザーとして動作する Webhook)。user を併せて渡してください。
  • overrideAccess: true - 信頼されたシステム作業(cron ジョブ、シード、マイグレーション、システムタスク、および完全な権限を意図的に付与された独立して認証された Webhook)

習慣や近くの呼び出しをコピーして overrideAccess: true を設定しないでください — この呼び出しが意味する値を選んでください。

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',
        overrideAccess: true,
        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',
        overrideAccess: true,
        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',
        overrideAccess: true,
        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',
        overrideAccess: true,
        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 は payload-types.ts を自動生成します — generate:types を手動で実行する必要はほとんどありません。

  • 開発中: typescript.autoGenerate のデフォルトは true であるため、設定が変更されるたびに開発サーバーが型を自動的に再生成します。開発サーバーの実行中に generate:types を手動で実行しないでください — 冗長です。
  • ビルド中: payload build は next build を実行する前にインポートマップと型を生成します。どちらも古くならないように、next build を直接呼び出すよりもこちらを優先してください。型生成をスキップするには --no-types を渡します。
  • 手動生成 (payload generate:types) は緊急時の手段です — 開発サーバーもビルドも関与しない場合 (たとえば単発のスクリプトや、payload build を実行しないステップの前の CI) にのみ使用してください。
// payload.config.ts
export default buildConfig({
  typescript: {
    outputFile: path.resolve(dirname, 'payload-types.ts'),
    // autoGenerate defaults to true — types regenerate in dev automatically
  },
})

// Usage
import type { Post, User } from '@/payload-types'

よくある落とし穴

  1. オプションの overrideAccess を伴うローカル API 操作は、デフォルトでアクセス制御に従います — 信頼できるサーバーサイドの処理にのみ true に設定してください
  2. ネストされた操作で req が欠落していると、トランザクションの原子性が損なわれます
  3. フックループ — フック内の操作が同じフックを再トリガーする可能性があります。req.context フラグを使用してください
  4. フィールドレベルのアクセスは真偽値のみを返し、クエリ制約はありません
  5. リレーションの深さはデフォルトで 2 です。ID のみにする場合は depth: 0 を設定してください
  6. 下書きステータス — 下書きが有効な場合、_status フィールドが自動的に注入されます
  7. 型は開発時 (autoGenerate) および payload build 中に自動的に再生成されます — generate:types を手動で実行することは避けてください
  8. MongoDB のトランザクションにはレプリカセットの設定が必要です
  9. SQLite のトランザクションはデフォルトで無効です。transactionOptions: {} で有効にしてください
  10. ポイントフィールドは SQLite ではサポートされていません
  11. 計算された仮想タイトル — virtual: true で設定されたフィールドは admin.useAsTitle で使用できません。保存されたトップレベルのフィールドを使用してください

ベストプラクティス

コンテンツモデリング
  • コンテンツコレクションではデフォルトで versions: { drafts: true } を有効にし、カスタムの status フィールドを追加するのではなく、自動注入される _status フィールドに依存してください
  • スラッグには、独自の一意なテキストフィールドを手作りするのではなく、ネイティブの slug フィールド型を使用してください
  • admin.useAsTitle には保存されたトップレベルのフィールドを使用してください。計算された virtual: true フィールドをタイトルとして使用しないでください
  • position: 'sidebar' は短く一目でわかるフィールド (ステータス、カテゴリ、著者、日付) 用に取っておき、長いフィールド (説明、リッチテキスト) はメインエリアに配置してください
セキュリティ
  • 制限的なアクセスをデフォルトとし、徐々に権限を追加してください
  • 呼び出しがユーザーのために動作する場合は常に overrideAccess: false を使用し、その user を渡してください
  • フィールドレベルのアクセスは真偽値のみを返します (クエリ制約はありません)
  • クライアントから提供されたデータを決して信頼しないでください
  • データベースのルックアップを避けるために、ロールには saveToJWT: true を使用してください
パフォーマンス
  • 頻繁にクエリされるフィールドにインデックスを付けてください
  • 返されるフィールドを制限するには select を使用してください
  • 過剰なフェッチを防ぐために、リレーションに maxDepth を設定してください
  • アクセス制御では、非同期操作よりもクエリ制約を優先してください
  • 負荷の高い操作は req.context にキャッシュしてください
データ整合性
  • フック内のネストされた操作には常に req を渡してください
  • 無限のフックループを防ぐためにコンテキストフラグを使用してください
  • MongoDB (レプリカセットが必要) と Postgres でトランザクションを有効にしてください
  • データのフォーマットには beforeValidate を使用してください
  • ビジネスロジックには beforeChange を使用してください
型安全性
  • dev(autoGenerate)と payload build に型を生成させ、どちらも実行されていない場合にのみ generate:types を手動で実行してください
  • 生成された payload-types.ts から型をインポートしてください
  • ユーザーオブジェクトに型を付けてください: import type { User } from '@/payload-types'
  • 実行時の型チェックにはフィールド型ガードを使用してください
  • Payload の値を名前付き定数に抽出する場合 — コレクション、フィールド、フック、アクセス関数、プラグインなど — は、対応する 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 API
  • ENDPOINTS.md - カスタム API エンドポイント: 認証、ヘルパー、リクエスト/レスポンスパターン
  • ADAPTERS.md - データベース、ストレージ、メールアダプター、トランザクション
  • ADVANCED.md - 認証、ジョブ、エンドポイント、コンポーネント、プラグイン、ローカライゼーション
  • PLUGIN-DEVELOPMENT.md - プラグインアーキテクチャ、モノレポ構成、パターン、ベストプラクティス

リソース

コメント