219 lines
7.6 KiB
Markdown
219 lines
7.6 KiB
Markdown
---
|
|
tags: [payloadcms, tech-patterns]
|
|
topic: payloadcms
|
|
sources: [database__overview.md, database__postgres.md, database__mongodb.md, database__sqlite.md, database__migrations.md, database__indexes.md, database__transactions.md]
|
|
created: 2026-05-15
|
|
---
|
|
|
|
# PayloadCMS — Database
|
|
|
|
## Overview
|
|
|
|
- Payload is **database-agnostic** — integrates via swappable Database Adapters
|
|
- Adapter translates Payload's internal data structures to the native DB format
|
|
- `db` property in [[wiki/payloadcms/configuration|Configuration]] accepts the adapter instance
|
|
- All major Payload features (localization, arrays, blocks, versions) work across all official adapters
|
|
- Exception: `Point` field not yet supported in SQLite
|
|
|
|
## Supported Databases
|
|
|
|
| Feature | MongoDB | Postgres | SQLite |
|
|
|---|---|---|---|
|
|
| Adapter pkg | `@payloadcms/db-mongodb` | `@payloadcms/db-postgres` | `@payloadcms/db-sqlite` |
|
|
| ORM | Mongoose | Drizzle + node-postgres | Drizzle + libSQL |
|
|
| Migrations required | Rarely | Always | Always |
|
|
| Schema enforced at DB level | No | Yes | Yes |
|
|
| Transactions | Requires replica set | Yes | Disabled by default |
|
|
| Point field | Yes | Yes | No |
|
|
| `db push` (dev mode) | N/A | Yes | Yes |
|
|
|
|
**Prefer MongoDB when:**
|
|
- Lots of dynamic fields, arrays/blocks, or heavy localization
|
|
- Don't want to manage DDL migrations
|
|
- Comfortable letting Payload enforce data integrity
|
|
|
|
**Prefer Postgres/SQLite when:**
|
|
- Flat, stable schema with strong DB-level constraints
|
|
- Need enforced foreign key relationships
|
|
- Comfortable with migration workflow
|
|
|
|
## Setup / Config
|
|
|
|
### MongoDB
|
|
|
|
```ts
|
|
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
|
|
|
export default buildConfig({
|
|
db: mongooseAdapter({
|
|
url: process.env.DATABASE_URL,
|
|
}),
|
|
})
|
|
```
|
|
|
|
Key options: `transactionOptions`, `migrationDir`, `connectOptions`, `collation`, `allowAdditionalKeys`
|
|
|
|
Compatibility shims for DocumentDB, Cosmos DB, Firestore:
|
|
```ts
|
|
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
|
|
db: mongooseAdapter({ url: process.env.DATABASE_URL, ...compatibilityOptions.firestore })
|
|
```
|
|
|
|
### Postgres
|
|
|
|
```ts
|
|
import { postgresAdapter } from '@payloadcms/db-postgres'
|
|
|
|
export default buildConfig({
|
|
db: postgresAdapter({
|
|
pool: { connectionString: process.env.DATABASE_URL },
|
|
}),
|
|
})
|
|
```
|
|
|
|
Vercel variant: `@payloadcms/db-vercel-postgres` — auto-uses `process.env.POSTGRES_URL`, falls back to `pg` for localhost.
|
|
|
|
Key options: `pool*`, `push`, `migrationDir`, `idType` (`serial`|`uuid`), `transactionOptions`, `readReplicas`, `readReplicasAfterWriteInterval` (default 2000ms), `blocksAsJSON`, `schemaName`
|
|
|
|
### SQLite
|
|
|
|
```ts
|
|
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
|
|
|
export default buildConfig({
|
|
db: sqliteAdapter({
|
|
client: {
|
|
url: process.env.DATABASE_URL,
|
|
authToken: process.env.DATABASE_AUTH_TOKEN,
|
|
},
|
|
}),
|
|
})
|
|
```
|
|
|
|
Key options: `client*`, `push`, `migrationDir`, `idType` (`number`|`uuid`), `transactionOptions`, `wal`, `autoIncrement`, `blocksAsJSON`, `busyTimeout`
|
|
|
|
Cloudflare D1 (beta): `@payloadcms/db-d1-sqlite` with `binding: cloudflare.env.D1`
|
|
|
|
Enable WAL mode for better concurrency:
|
|
```ts
|
|
db: sqliteAdapter({ wal: true, client: { url: process.env.DATABASE_URL } })
|
|
```
|
|
|
|
## Migrations
|
|
|
|
Default storage path: `./src/migrations`. Override via `migrationDir` in adapter options.
|
|
|
|
Each migration file exports `up` and `down` functions (TypeScript):
|
|
```ts
|
|
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
|
|
|
|
export async function up({ payload, req }: MigrateUpArgs): Promise<void> { /* ... */ }
|
|
export async function down({ payload, req }: MigrateDownArgs): Promise<void> { /* ... */ }
|
|
```
|
|
|
|
### CLI Commands
|
|
|
|
```bash
|
|
npm run payload migrate # run pending migrations
|
|
npm run payload migrate:create [name] # create new migration file
|
|
npm run payload migrate:status # show which migrations ran / pending
|
|
npm run payload migrate:down # roll back last batch
|
|
npm run payload migrate:refresh # roll back all, re-run all
|
|
npm run payload migrate:reset # roll back all
|
|
npm run payload migrate:fresh # drop all entities, re-run from scratch
|
|
```
|
|
|
|
`migrate:create` flags: `--skip-empty`, `--force-accept-warning`
|
|
|
|
Requires npm script in `package.json`:
|
|
```json
|
|
{ "scripts": { "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload" } }
|
|
```
|
|
|
|
### Recommended CI workflow (Postgres)
|
|
|
|
```json
|
|
{ "scripts": { "ci": "payload migrate && pnpm build" } }
|
|
```
|
|
|
|
### Runtime migrations (long-running servers)
|
|
|
|
```ts
|
|
import { migrations } from './migrations'
|
|
db: postgresAdapter({ prodMigrations: migrations })
|
|
```
|
|
|
|
Avoid on serverless — slows cold starts.
|
|
|
|
### MongoDB vs Postgres migration strategy
|
|
|
|
- **MongoDB**: only needed when transforming existing data from shape A → B; run locally against prod DB or in CI
|
|
- **Postgres**: required for every schema change; use `push` mode locally (don't mix push + migrate on same DB)
|
|
|
|
### Environment-specific config gotcha
|
|
|
|
If a plugin is enabled only in production, generate migrations with prod env vars active — otherwise the migration will be incomplete.
|
|
|
|
## Indexes & Transactions
|
|
|
|
### Indexes
|
|
|
|
Field-level index:
|
|
```ts
|
|
{ name: 'title', type: 'text', index: true }
|
|
```
|
|
|
|
Compound index (collection config):
|
|
```ts
|
|
indexes: [{ fields: ['title', 'createdAt'], unique: true }]
|
|
```
|
|
|
|
- `id`, `createdAt`, `updatedAt` are indexed by default
|
|
- `unique: true` creates a collection-wide unique DB index
|
|
- Localized fields with `index: true` on MongoDB → one index per locale path; can hit per-collection index limit with many locales
|
|
|
|
### Transactions
|
|
|
|
- Enabled by default for all write operations where the DB supports it
|
|
- MongoDB: requires replica set connection
|
|
- SQLite: **disabled by default** — enable with `transactionOptions: {}`
|
|
- Pass `req` into Local API calls to participate in the same transaction:
|
|
|
|
```ts
|
|
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
|
|
await req.payload.create({ req, collection: 'my-slug', data: { some: 'data' } })
|
|
}
|
|
```
|
|
|
|
- Do **not** pass `req` for fire-and-forget (unawaited) calls — rolled-back transaction would still return OK
|
|
|
|
Direct transaction control:
|
|
```ts
|
|
const transactionID = await payload.db.beginTransaction()
|
|
try {
|
|
await payload.update({ collection: 'posts', data: {}, where: {}, req: { transactionID } })
|
|
await payload.db.commitTransaction(transactionID)
|
|
} catch {
|
|
await payload.db.rollbackTransaction(transactionID)
|
|
}
|
|
```
|
|
|
|
Disable per-operation: `disableTransaction: true` in Local API args.
|
|
Disable globally: `transactionOptions: false` in adapter config.
|
|
|
|
## Gotchas
|
|
|
|
- **Do not mix `push` and `migrate`** on the same local dev database — Payload will warn and it causes drift
|
|
- **Azure Cosmos DB**: no cross-collection transactions; requires `indexSortableFields: true` in root config
|
|
- **`unique` on array/blocks nested fields**: creates collection-wide uniqueness, not per-document; on MongoDB with `required: true` → non-sparse index causes duplicate key errors on empty arrays
|
|
- **Vercel Postgres adapter**: uses `pg` (not `@vercel/postgres`) for localhost URLs; use `forceUseVercelPostgres: true` to override
|
|
- **Schema hooks (`beforeSchemaInit`/`afterSchemaInit`)**: columns/tables added via hooks do NOT appear in `payload generate:db-schema` output — mutate `adapter.rawTables` inside `beforeSchemaInit` if you need them in the generated schema
|
|
- **`prodMigrations`**: runtime migration on serverless = slow cold starts; only use for long-running containers
|
|
- **Environment-specific plugins**: migrations generated without prod env vars will miss plugin-added fields
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/configuration|Configuration]]
|
|
- [[wiki/payloadcms/getting-started|Getting Started]]
|
|
- [[wiki/payloadcms/fields|Fields]]
|
|
- [[wiki/payloadcms/hooks|Hooks]]
|