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