# 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, }), }); ```