# Payload CMS Adapters Reference Complete reference for database, storage, and email adapters. ## Database Adapters ### MongoDB ```ts import { mongooseAdapter } from '@payloadcms/db-mongodb' export default buildConfig({ db: mongooseAdapter({ url: process.env.DATABASE_URL, }), }) ``` ### Postgres ```ts 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 ```ts 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. ```ts 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. ```ts 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 - **@payloadcms/storage-uploadthing** - Uploadthing ### AWS S3 ```ts 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 ```ts 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 ```ts 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 ```ts 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 ```ts import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob' export default buildConfig({ plugins: [ vercelBlobStorage({ collections: { media: true, }, token: process.env.BLOB_READ_WRITE_TOKEN, }), ], }) ``` ### Uploadthing ```ts 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) ```ts 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 ```ts import { resendAdapter } from '@payloadcms/email-resend' export default buildConfig({ email: resendAdapter({ defaultFromAddress: 'noreply@example.com', defaultFromName: 'My App', apiKey: process.env.RESEND_API_KEY, }), }) ```