メインコンテンツまでスキップ

Admin / Media / Deploy — 運用層

この章のゴール

  • Admin UI のカスタマイズ可能ポイントを掴む (ロゴ / Custom Components / theme)
  • Plugin エコシステムの全体像と、よく使うプラグインを知る
  • Media (Uploads) を ローカル → S3 に切り替える基本パターンを書ける
  • Postgres adapter の運用 (db.push vs payload migrate) を理解する
  • Jobs Queue でバックグラウンド処理・定期実行を組む最小形を知る
  • Vercel / Docker / Railway / Fly へのデプロイの分岐点を持つ
  • AI Auto-Embedding の概観を掴み、シリーズを締める

7.1 Admin UI カスタマイズ

公式 doc (Admin Overview) によれば、Admin Panel は TypeScript + React で構築され、Next.js App Router 上の (payload) route group から動作します。admin プロパティを通じてカスタマイズ可能です。

admin プロパティの主要オプション

オプション役割
user認証 Collection の指定 ('admins' など)
componentsUI 置換 / 差し込み
livePreviewリアルタイムプレビュー設定 (06 章)
autoLogin開発時の自動ログイン
avatarプロフィール画像表示
dateFormat日付表示形式
theme強制テーマ ('light' | 'dark' | 'all')

Custom Components の差し込み

import { buildConfig } from 'payload'

export default buildConfig({
admin: {
user: 'users',
components: {
beforeNavLinks: ['./components/CustomNavBanner'],
afterDashboard: ['./components/DashboardWidget'],
graphics: {
Logo: './components/Logo',
Icon: './components/Icon',
},
},
},
// ...
})

beforeNavLinks / afterNavLinks / beforeDashboard / afterDashboard などの差し込みポイントが用意されており、自分の React コンポーネントを Server / Client いずれの形式でも入れ込めます。graphics.Logoロゴ差し替えcustom.scss で CSS オーバーライドという形で white-label ブランディングも実現できます。

3.x 中期以降の Admin 機能 (概観)

3.x の途中で Admin Panel まわりの管理系機能が随時追加されています。深掘りは公式 doc に譲り、存在だけ押さえておくと選定・運用判断に効きます:

  • Trash (ソフトデリート) — Collection に trash: true を付けると、削除が deletedAt 付きの「ごみ箱入り」になり、Admin UI の Trash ビューから復元・完全削除できます (Trash)
  • Folders (beta) — Collection 横断でドキュメントをフォルダ分けする整理機能。Collection 側に folders: true、config 側に folders 設定を書きます (Folders。beta のため minor update で変わる可能性が公式に明記されています)
  • Query Presets — 一覧のフィルタ・カラム・ソートの組を保存してチームで共有する機能。Collection に enableQueryPresets: true で有効化します (Query Presets)

7.2 Plugin エコシステム

公式 doc (Plugins Overview) によれば、Plugin は config.plugins 配列に登録するだけで効きます。Plugin の関数シグネチャは「既存の Config を受け取って変更して返す」純粋関数です:

type Plugin = (incomingConfig: Config) => Config

Payload チーム公式 Plugin (主要)

Plugin用途
Form Builderフォーム定義を Admin UI で組み立てて、フロントに JSON 配信
SEOOGP / メタタグ / sitemap 用フィールドの一括追加
Search全文検索向け index Collection の自動構築
Nested Docs階層構造を持つ Collection (ページツリー等) のサポート
Redirectsリダイレクトルールを Admin UI から管理
StripeStripe 連携 (顧客・サブスクリプション同期)
Multi-Tenantマルチテナント (テナントごとにデータを分離)
Sentryエラーレポーティング
Import / Exportデータのインポート・エクスポート
MCPModel Context Protocol 連携
EcommerceE コマース構築用一式
Cloud Storage旧来の汎用 storage plugin (S3 単体なら次節の @payloadcms/storage-s3 推奨)

使い方の例

import { buildConfig } from 'payload'
import { seoPlugin } from '@payloadcms/plugin-seo'
import { searchPlugin } from '@payloadcms/plugin-search'

export default buildConfig({
collections: [/* ... */],
plugins: [
seoPlugin({
collections: ['posts', 'pages'],
generateTitle: ({ doc }) => `${doc.title} | My Site`,
}),
searchPlugin({
collections: ['posts'],
}),
],
})

Plugin は Config が正規化される前に走るので、自分の Plugin で Collection を追加したり、既存 Collection に Hook を注入したりできます。

7.3 Media (Uploads) — ローカル vs S3

Upload-enabled Collection の基本

公式 doc (Upload Overview) によれば、upload プロパティを付けると、Collection は ファイルアップロード機能を持ち、filename / mimeType / filesize が自動で追加されます。

import path from 'path'

export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticDir: path.resolve(__dirname, '../media'),
mimeTypes: ['image/*'],
imageSizes: [
{ name: 'thumbnail', width: 400, height: 300, position: 'centre' },
{ name: 'card', width: 768, height: undefined }, // 高さ自動
{ name: 'hero', width: 1920, height: 1080 },
],
adminThumbnail: 'thumbnail',
focalPoint: true,
},
fields: [
{ name: 'alt', type: 'text' },
],
}

imageSizes の自動リサイズ

公式 doc によれば、imageSizes 配列に定義したサイズが Sharp ライブラリで自動生成されます。height: undefined でアスペクト比保持、position でクロップ位置、withoutEnlargement で「元画像より小さい場合の挙動」を制御できます。

S3 への切り替え

@payloadcms/storage-s3 プラグインで S3 (および S3 互換ストレージ) に切り替えられます。これは Payload 2.x 時代に必要だった @payloadcms/plugin-cloud-storage の後継です。

import { s3Storage } from '@payloadcms/storage-s3'

export default buildConfig({
collections: [Media],
plugins: [
s3Storage({
collections: {
media: true, // Media collection を S3 に
},
bucket: process.env.S3_BUCKET!,
config: {
region: process.env.S3_REGION!,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
},
},
clientUploads: true, // Vercel の 4.5MB body 制限を回避 (後述)
}),
],
})

config@aws-sdk/client-s3S3ClientConfig をそのまま渡せるため、Cloudflare R2 / DigitalOcean Spaces / Backblaze B2 など S3 互換のあらゆるストレージを同じ syntax で使えます。

バケット自体の設計 (Block Public Access・暗号化・バージョニング・CORS・presigned URLの前提となる設定) は、AWS実践ガイド 第5章: S3 で扱っています。

clientUploads の意義

Vercel のサーバ関数には body サイズに 4.5MB 制限があり、大きい画像を Admin UI からアップロードすると失敗します。clientUploads: true を付けると ブラウザから直接 S3 へ署名付き URL でアップロードされるため、サーバを経由せず制限を回避できます。Vercel 上で運用する場合の事実上の必須設定です。

7.4 DB adapter の選定

adapterパッケージ用途・特徴
Postgres@payloadcms/db-postgres3.x で stable / Drizzle ORM + node-postgres / 本シリーズの主軸
Vercel Postgres@payloadcms/db-vercel-postgresVercel Postgres に最適化
MongoDB@payloadcms/db-mongodbPayload 1.x からの伝統 / スキーマ柔軟
SQLite@payloadcms/db-sqliteローカル開発 / 小規模運用 / トランザクションはデフォルト無効

迷ったら 本番 RDB を持ちたいなら Postgresスキーマ進化が多いなら MongoDB開発機で完結させたいなら SQLite が基本指針です。

7.5 Migration 戦略

公式 doc (Migrations) によれば、Postgres / SQLite adapter は Drizzle ORM の migration 機構を流用しています。

コマンド一覧

コマンド用途
payload migrate未実行の migration を順に適用
payload migrate:create新しい migration ファイルを生成 (現在のスキーマ差分から)
payload migrate:status適用状況の確認
payload migrate:down最後のバッチをロールバック
payload migrate:refresh全ロールバック後に再適用
payload migrate:reset全 migration ロールバック
payload migrate:freshDB を完全リセットして再構築

Migration ファイル

// migrations/20260616_add_posts_status.ts
import type { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'

export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
// スキーマ変更を実行
}

export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
// up を巻き戻す処理
}

dev push vs production migrate

モード適用先用途
db push (db.push: true、デフォルト dev で有効)ローカル DBスキーマ変更を即時反映、migration ファイル不要
payload migrate本番 DBCI/CD で実行、migration ファイル必須

公式 doc は **「push と migrate を同じローカル DB で混在させないこと」**を明示警告しています。ローカルでは push で素早く回し、リリース前に migrate:create で migration ファイルを切る、というフローが標準です。

CI/CD の典型形

// package.json
{
"scripts": {
"build": "next build",
"ci": "payload migrate && pnpm build"
}
}

デプロイの前段で payload migrate を必ず走らせることで、production DB がスキーマ更新を取り逃さない構成にできます。

7.6 Jobs Queue (バックグラウンド処理)

メール送信・外部 API 連携・重い集計のような「リクエストの外で走らせたい処理」のために、PayloadCMS は Jobs Queue を core 機能として持っています (Jobs Queue (公式 doc))。外部のジョブ基盤を別途立てずに、config だけで task 定義から定期実行まで完結します。

Task の定義と queue

jobs.tasks に slug / inputSchema / handler を定義し、payload.jobs.queue() で積みます:

// payload.config.ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
slug: 'sendEmail',
inputSchema: [
{ name: 'userId', type: 'text', required: true },
],
handler: async ({ input, req }) => {
const user = await req.payload.findByID({
collection: 'users',
id: input.userId,
})
await req.payload.sendEmail({
to: user.email,
subject: 'Welcome!',
html: '...',
})
return { output: {} }
},
},
],
},
})
// Server Action / Hook / カスタムエンドポイントから積む
await payload.jobs.queue({
task: 'sendEmail',
input: { userId: '123' },
waitUntil: new Date('2026-12-25T00:00:00Z'), // (任意) 実行を遅らせる
})

cron スケジュールと autoRun

task 定義に schedule を付けると cron 周期で queue に積まれ、autoRun の runner が処理します:

jobs: {
tasks: [
{
slug: 'dailyDigest',
schedule: [{ cron: '0 8 * * *', queue: 'daily' }], // 毎日 8:00 に積む
handler: async ({ req }) => {
/* ... */
return { output: {} }
},
},
],
autoRun: [
{ cron: '* * * * *', queue: 'daily', limit: 10 }, // 毎分 queue を確認して実行
],
}

schedule (積む側) と autoRun (実行する側) は同じ queue 名を指す必要があります。

serverless (Vercel) での注意

公式 doc は autoRun を「常時起動している専用サーバ向け」とし、Vercel のような serverless プラットフォームでは使わないよう明示しています (Queues (公式 doc))。serverless では、ジョブ実行用に用意される GET /api/payload-jobs/run エンドポイントを外部スケジューラから定期的に叩きます。Vercel なら vercel.jsoncrons にこのパスを登録し、環境変数 CRON_SECRET を設定すると Authorization: Bearer ${CRON_SECRET} が自動送信されてエンドポイントが保護されます。常駐 Node サーバ (7.7 節の Docker / VPS) なら autoRun がそのまま機能します。

複数 task の連鎖 (workflows) やリトライ制御などの詳細は公式 doc に一式あります。本節は「Jobs Queue が core にある」ことを押さえれば十分です。

7.7 デプロイ — 4 つの選択肢

PayloadCMS は Node.js が動くなら基本どこでもデプロイ可能 (Edge は不可、06 章)。主な選択肢を比較します。

Vercel

最も手早い選択肢。Vercel Postgres + @payloadcms/db-vercel-postgres で完結します。

注意点:

  • Function timeout: Vercel Functions は fluid compute (デフォルト有効) で デフォルト 300 秒 (5 分)。上限は Hobby 300 秒 / Pro・Enterprise 800 秒、ベータの extended max duration を使うと最大 1,800 秒 (30 分) (Vercel: Configuring Maximum Duration、2026-07 時点)。大量データの import / migrate には注意
  • Body 4.5MB 制限: Admin UI から大きな画像をアップすると失敗する。@payloadcms/storage-s3clientUploads: true で回避 (7.3 節)
  • Cold start: function ベースのため、長時間アクセスがないと初回 request が遅くなる
// vercel.json
{
"functions": {
"app/(payload)/api/**/route.ts": { "maxDuration": 60 }
}
}

Docker / 自前 Node サーバ

editor が頻繁にアクセスする用途・Long-running 処理がある場合は VPS 上の Node サーバが向きます。Cold start なし、function timeout なし、body サイズ制限なし。

公式 doc が示す Docker 例 (node:24-alpine、multi-stage、output: 'standalone' 使用):

# 抜粋
FROM node:24-alpine AS base
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build

FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=base /app/.next/standalone ./
COPY --from=base /app/.next/static ./.next/static
COPY --from=base /app/public ./public
CMD ["node", "server.js"]

next.configoutput: 'standalone' を付けるのを忘れないこと。

Railway / Fly / Render

「managed container」系。Docker と Vercel の中間的な選択肢で、container を build → 立ち上げてくれる、コストは Vercel より読みやすい、timeout 制約なし。Payload も「Next.js が動くところならどこでも動く」と公式が表明しています。

Cloudflare Workers (D1 + R2)

2025 年 10 月 3 日に Cloudflare Workers へのワンクリックデプロイ対応が公開されました (公式ブログ)。公式テンプレート with-cloudflare-d1 が用意され、Workers + D1 (DB) + R2 (Storage) の組み合わせで動かせます。

制約:

  • Workers Paid Plan 必須 (Free は 3MB の Workers サイズ制限を超えるため不可)
  • pino-pretty が Node API に依存するため、custom console logger に置き換える必要あり
  • GraphQL は upstream の Workers 側 issue で現状動かないケースあり (template 同梱の解説参照)
  • Page Router 系の Pages (旧 Cloudflare Pages) は対象外、Workers 経由のデプロイ

「Edge runtime で動かない」という制約 (06 章 6.5) は本質として残るため、Cloudflare 上でも内部的には Node 互換ランタイムを介した形になります。本シリーズでは要件が合えば検討、というスタンスです。

7.8 Payload Cloud (新規受付停止中)

Figma 買収 (2025 年 6 月) 以降、Payload Cloud の新規 sign-up は停止しています。既存ユーザーはアクセスを継続できますが、新規プロジェクトは self-host を前提に上記 4 つの選択肢から選ぶ必要があります (出典: Payload is joining Figma! (公式ブログ) / GitHub Discussion #12843)。

OSS / self-host が PayloadCMS の主軸であることを考えると、Payload Cloud に依存しない方が長期的には安全な選択です。

7.9 AI Auto-Embedding (概観)

公式 doc トップ (What is Payload) には、エンタープライズ機能として AI Auto-Embedding が列挙されています。Collection のコンテンツを ベクトル埋め込みとして自動生成し、RAG (Retrieval-Augmented Generation) のデータソースとして使える機能です。

OSS コアで RAG 連携を組む場合、自前で:

  • Hook (afterChange) でテキスト抽出
  • 埋め込み API (OpenAI / Cohere / Voyage 等) を叩く
  • ベクトル DB (pgvector / Pinecone / Weaviate 等) に保存
  • 検索エンドポイントを endpoints で生やす

という構成も可能ですが、エンタープライズ版なら一気に省力化できる、という位置付けです。

7.10 環境変数の整理

PayloadCMS を本番で動かす場合の最小セット:

# Payload 本体
PAYLOAD_SECRET=本番用に十分長いランダム文字列 (32 文字以上推奨)
DATABASE_URL=postgresql://user:pass@host:5432/db
PAYLOAD_PUBLIC_SERVER_URL=https://example.com

# プレビュー
PREVIEW_SECRET=draft preview 用の独自トークン

# Media (S3 使う場合)
S3_BUCKET=my-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY=...
S3_SECRET_KEY=...

# Next.js 公開側
NEXT_PUBLIC_SERVER_URL=https://example.com

PAYLOAD_SECRET十分長くランダムにすること、PAYLOAD_CONFIG_PATH は config を非標準位置に置いている場合のみ。

実装例 — Postgres migration の raw SQL 実行 (up / down)

payload migrate:create で生成される migration ファイルは、updown の 2 関数を export する形になっています。Postgres adapter では、sql template literal を db.execute() に渡すことで生 SQL を流せます (Migrations doc)。

// migrations/20260616_100000_add_archived_flag.ts
import { type MigrateUpArgs, type MigrateDownArgs, sql } from '@payloadcms/db-postgres'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
// 既存のスキーマを payload generate:db-schema で更新 → migrate:create で反映するのが基本だが、
// データ補正・index 追加など raw SQL が必要なケースで sql テンプレートを使う
await db.execute(sql`
ALTER TABLE posts
ADD COLUMN archived BOOLEAN NOT NULL DEFAULT false
`)

// 既存データを Local API で touch する例 (公式 doc は Postgres の例で `SELECT * from posts` を示している)
const { rows: posts } = await db.execute(sql`SELECT id FROM posts WHERE _status = 'published'`)
payload.logger.info({ migrated: posts.length }, 'archived flag initialized')
}

export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE posts
DROP COLUMN archived
`)
}

ポイント:

  • import { sql } from '@payloadcms/db-postgres' — Postgres adapter 由来の sql template literal (SQLite なら @payloadcms/db-sqlite から同名で取れる)
  • db.execute()sql template literal を渡して SQL を実行、戻り値は { rows: T[] }
  • down 関数で up を巻き戻す処理を書く — production で問題発生時にロールバック可能に
  • payload.logger で migration の進捗をログに残す (04 章 監査ログパターン と同じ pino style)
Drizzle ORM 直接利用は公式 doc 未明示

公式 doc は raw SQL を流す例 (上記) を主に示しており、Drizzle ORM の query builder API を使った migration の完全例は未掲載です。型安全に書きたい場合は Drizzle 公式 doc を併読してください。

出典: Migrations (公式 doc)

この章のまとめ

  • Admin UI は admin.components で部分置換、graphics.Logo でブランディング、custom.scss で全体カスタマイズ
  • Plugin は config.plugins 配列に並べるだけ、公式に SEO / Form Builder / Search / Stripe / Multi-Tenant / Sentry など多数
  • Media は ローカル → S3 (@payloadcms/storage-s3) に s3Storage() 1 行で切り替え可、Vercel では clientUploads: true が事実上必須
  • Postgres adapter は dev pushpayload migrate絶対に混在させない、CI で migrate を必ず実行
  • デプロイは Vercel / Docker / Railway 系 / 自前 Node から目的別に選ぶ。Edge runtime は基本不可、Cloudflare は Workers (Paid + 制約) で対応
  • Jobs Queue が core にある: task 定義 + payload.jobs.queue() + schedule / autoRun。serverless では autoRun でなく /api/payload-jobs/run を外部 cron から叩く
  • Admin 系は 3.x 中期以降 Trash / Folders (beta) / Query Presets が追加 — 必要になったら公式 doc へ
  • Payload Cloud は 2025-06 以降新規受付停止、self-host が標準
  • AI Auto-Embedding はエンタープライズ機能、OSS でも Hook + ベクトル DB で自前実装可
  • Migration は import { sql } from '@payloadcms/db-postgres' + db.execute()sql template literal を渡して raw SQL を流せる (up / down を対称に書く)

シリーズのまとめ

7 章を通して、PayloadCMS を「Next.js プロジェクトに同居させて、データモデルから運用まで TypeScript 1 枚で書く」道筋を辿ってきました。

次のステップは、自分のプロジェクトで create-payload-app を走らせて、最小の Collection 1 つから始めることです。公式 doc の各機能ページを横に置きながら、本シリーズで掴んだ全体像と突き合わせて読むと、知識が定着しやすいはずです。

参考