327 lines
7.3 KiB
Markdown
327 lines
7.3 KiB
Markdown
# 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,
|
|
}),
|
|
})
|
|
```
|