7.3 KiB
7.3 KiB
Payload CMS 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.contextorreq.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
- @payloadcms/storage-uploadthing - Uploadthing
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,
}),
],
})
Uploadthing
import { uploadthingStorage } from '@payloadcms/storage-uploadthing'
export default buildConfig({
plugins: [
uploadthingStorage({
collections: {
media: true,
},
options: {
token: process.env.UPLOADTHING_TOKEN,
acl: 'public-read',
},
}),
],
})
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,
}),
})