payload

Use when working with Payload projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.

Install
npx skills add 'https://github.com/payloadcms/payload/tree/main/packages/payload/skills/payload'
Download bundle ↓
main · 9a2cdcbScanned 2026-09-17

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md

Payload Adapters Reference

Complete reference for database, storage, and email adapters.

Database Adapters

MongoDB

import { mongooseAdapter } from '@payloadcms/db-mongodb'

export default buildConfig({
  db: mongooseAdapter({
    url: process.env.DATABASE_URL,
  }),
})

Postgres

import { postgresAdapter } from '@payloadcms/db-postgres'

export default buildConfig({
  db: postgresAdapter({
    pool: {
      connectionString: process.env.DATABASE_URL,
    },
    push: false, // Don't auto-push schema changes
    migrationDir: './migrations',
  }),
})

SQLite

import { sqliteAdapter } from '@payloadcms/db-sqlite'

export default buildConfig({
  db: sqliteAdapter({
    client: {
      url: 'file:./payload.db',
    },
    transactionOptions: {}, // Enable transactions (disabled by default)
  }),
})

Transactions

Payload automatically uses transactions for all-or-nothing database operations. Pass req to include operations in the same transaction.

import type { CollectionAfterChangeHook } from 'payload'

const afterChange: CollectionAfterChangeHook = async ({ req, doc }) => {
  // This will be part of the same transaction
  await req.payload.create({
    req, // Pass req to use same transaction
    collection: 'audit-log',
    data: { action: 'created', docId: doc.id },
  })
}

// Manual transaction control
const transactionID = await payload.db.beginTransaction()
try {
  await payload.create({
    collection: 'orders',
    data: orderData,
    req: { transactionID },
  })
  await payload.update({
    collection: 'inventory',
    id: itemId,
    data: { stock: newStock },
    req: { transactionID },
  })
  await payload.db.commitTransaction(transactionID)
} catch (error) {
  await payload.db.rollbackTransaction(transactionID)
  throw error
}

Note: MongoDB requires replicaset for transactions. SQLite requires transactionOptions: {} to enable.

Threading req Through Operations

Critical: When performing nested operations in hooks, always pass req to maintain transaction context. Failing to do so breaks atomicity and can cause partial updates.

import type { CollectionAfterChangeHook } from 'payload'

// ✅ CORRECT: Thread req through nested operations
const resaveChildren: CollectionAfterChangeHook = async ({ collection, doc, req }) => {
  // Find children - pass req
  const children = await req.payload.find({
    collection: 'children',
    where: { parent: { equals: doc.id } },
    req, // Maintains transaction context
  })

  // Update each child - pass req
  for (const child of children.docs) {
    await req.payload.update({
      id: child.id,
      collection: 'children',
      data: { updatedField: 'value' },
      req, // Same transaction as parent operation
    })
  }
}

// ❌ WRONG: Missing req breaks transaction
const brokenHook: CollectionAfterChangeHook = async ({ collection, doc, req }) => {
  const children = await req.payload.find({
    collection: 'children',
    where: { parent: { equals: doc.id } },
    // Missing req - separate transaction or no transaction
  })

  for (const child of children.docs) {
    await req.payload.update({
      id: child.id,
      collection: 'children',
      data: { updatedField: 'value' },
      // Missing req - if parent operation fails, these updates persist
    })
  }
}

Why This Matters:

  • MongoDB (with replica sets): Creates atomic session across operations
  • PostgreSQL: All operations use same Drizzle transaction
  • SQLite (with transactions enabled): Ensures rollback on errors
  • Without req: Each operation runs independently, breaking atomicity

When req is Required:

  • All mutating operations in hooks (create, update, delete)
  • Operations that must succeed/fail together
  • When using MongoDB replica sets or Postgres
  • Any operation that relies on req.context or req.user

When req is Optional:

  • Read-only lookups independent of current transaction
  • Operations with disableTransaction: true
  • Administrative operations with overrideAccess: true

Storage Adapters

Available storage adapters:

  • @payloadcms/storage-s3 - AWS S3
  • @payloadcms/storage-azure - Azure Blob Storage
  • @payloadcms/storage-gcs - Google Cloud Storage
  • @payloadcms/storage-r2 - Cloudflare R2
  • @payloadcms/storage-vercel-blob - Vercel Blob

AWS S3

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

export default buildConfig({
  plugins: [
    s3Storage({
      collections: {
        media: true,
      },
      bucket: process.env.S3_BUCKET,
      config: {
        credentials: {
          accessKeyId: process.env.S3_ACCESS_KEY_ID,
          secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
        },
        region: process.env.S3_REGION,
      },
    }),
  ],
})

Azure Blob Storage

import { azureStorage } from '@payloadcms/storage-azure'

export default buildConfig({
  plugins: [
    azureStorage({
      collections: {
        media: true,
      },
      connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
      containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
    }),
  ],
})

Google Cloud Storage

import { gcsStorage } from '@payloadcms/storage-gcs'

export default buildConfig({
  plugins: [
    gcsStorage({
      collections: {
        media: true,
      },
      bucket: process.env.GCS_BUCKET,
      options: {
        projectId: process.env.GCS_PROJECT_ID,
        credentials: JSON.parse(process.env.GCS_CREDENTIALS),
      },
    }),
  ],
})

Cloudflare R2

import { r2Storage } from '@payloadcms/storage-r2'

export default buildConfig({
  plugins: [
    r2Storage({
      collections: {
        media: true,
      },
      bucket: process.env.R2_BUCKET,
      config: {
        credentials: {
          accessKeyId: process.env.R2_ACCESS_KEY_ID,
          secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
        },
        region: 'auto',
        endpoint: process.env.R2_ENDPOINT,
      },
    }),
  ],
})

Vercel Blob

import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'

export default buildConfig({
  plugins: [
    vercelBlobStorage({
      collections: {
        media: true,
      },
      token: process.env.BLOB_READ_WRITE_TOKEN,
    }),
  ],
})

Email Adapters

Nodemailer (SMTP)

import { nodemailerAdapter } from '@payloadcms/email-nodemailer'

export default buildConfig({
  email: nodemailerAdapter({
    defaultFromAddress: 'noreply@example.com',
    defaultFromName: 'My App',
    transportOptions: {
      host: process.env.SMTP_HOST,
      port: 587,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
    },
  }),
})

Resend

import { resendAdapter } from '@payloadcms/email-resend'

export default buildConfig({
  email: resendAdapter({
    defaultFromAddress: 'noreply@example.com',
    defaultFromName: 'My App',
    apiKey: process.env.RESEND_API_KEY,
  }),
})
Referenced from SKILL.md