obsidian/wiki/payloadcms/database-sqlite.md
2026-05-15 15:13:56 +01:00

199 lines
6.7 KiB
Markdown

---
title: "Database — SQLite"
aliases: [sqlite-adapter, payload-sqlite, db-sqlite]
tags: [payloadcms, database, sqlite, drizzle, libsql, turso, cloudflare-d1]
sources: [raw/database__sqlite.md]
created: 2026-05-15
updated: 2026-05-15
---
## Overview
Payload supports SQLite via `@payloadcms/db-sqlite`, built on **Drizzle ORM** + **libSQL**. Auto-manages schema changes in dev mode; full migration controls for other environments.
```ts
import { sqliteAdapter } from '@payloadcms/db-sqlite'
export default buildConfig({
db: sqliteAdapter({
client: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_AUTH_TOKEN, // for Turso
},
}),
})
```
## Key Options
| Option | Default | Notes |
|--------|---------|-------|
| `client` * | — | `{ url, authToken }` passed to `createClient` from `@libsql/client` |
| `push` | `true` (dev only) | Disable Drizzle db push in development |
| `migrationDir` | — | Custom migration directory |
| `idType` | `'number'` | `'number'` or `'uuid'` |
| `transactionOptions` | — | `SQLiteTransactionConfig` or `false` to disable |
| `localesSuffix` | `'_locales'` | Table name suffix for localized fields |
| `relationshipsSuffix` | `'_rels'` | Table name suffix for relationships |
| `versionsSuffix` | `'_v'` | Table name suffix for versions |
| `autoIncrement` | `false` | SQLite AUTOINCREMENT — prevents ID reuse from deleted rows |
| `blocksAsJSON` | `false` | Store blocks as JSON instead of relational tables (perf boost for many blocks) |
| `busyTimeout` | `0` | ms to wait when DB is locked |
| `wal` | `false` | Enable WAL mode — see [[wiki/payloadcms/database-sqlite#wal-mode\|WAL Mode]] |
| `beforeSchemaInit` | — | Hook before schema build |
| `afterSchemaInit` | — | Hook after schema build |
| `generateSchemaOutputFile` | `src/payload-generated.schema.ts` | Override generated schema path |
| `allowIDOnCreate` | `false` | Accept `id` in create operations without custom ID field |
## Dev Mode — db push vs Migrations
- **db push** (default): auto-syncs schema changes to DB, no manual migration needed; warns on data loss
- **Migrations only**: set `push: false` — use `payload migrate` commands exclusively
- Do **not** mix push and manual migrate commands
See [[wiki/payloadcms/database-migrations|Database Migrations]] for full CLI reference.
## Drizzle Direct Access
Generate types first:
```sh
npx payload generate:db-schema
```
Then query directly:
```ts
import { posts } from './payload-generated-schema'
import { eq, sql, and } from '@payloadcms/db-sqlite/drizzle'
// Relational API
const posts = await payload.db.drizzle.query.posts.findMany()
// Select API
const result = await payload.db.drizzle
.select()
.from(posts)
.where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`))
```
Exposed on `payload.db`:
- `payload.db.drizzle` — Drizzle instance
- `payload.db.tables` — all tables
- `payload.db.relations` — all relations
## Schema Hooks
### beforeSchemaInit
Runs before schema is built. Use to add tables Payload doesn't manage (e.g. preserving existing DB structure when migrating to Payload).
```ts
sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
addedTable: sqliteTable('added_table', {
id: integer('id').primaryKey({ autoIncrement: true }),
}),
},
}
},
],
})
```
Tip: use [Drizzle Introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull) to generate schema from an existing DB, then import into `beforeSchemaInit`.
To include custom tables/columns in `payload generate:db-schema` output, mutate `adapter.rawTables` inside `beforeSchemaInit`:
```ts
adapter.rawTables.posts.columns.customColumn = {
name: 'custom_column',
type: 'integer',
notNull: true,
}
```
### afterSchemaInit
Runs after schema is built. Use `extendTable` utility to add columns or composite indexes not in Payload config:
```ts
sqliteAdapter({
afterSchemaInit: [
({ schema, extendTable }) => {
extendTable({
table: schema.tables.places,
columns: { extraIntegerColumn: integer('extra_integer_column') },
extraConfig: (table) => ({
country_city_idx: index('country_city_idx').on(table.country, table.city),
}),
})
return schema
},
],
})
```
Note: columns/tables added in `afterSchemaInit` won't appear in `generate:db-schema` output — only `beforeSchemaInit` + `rawTables` mutation achieves that.
## WAL Mode
Write-Ahead Logging improves performance and concurrency:
```ts
db: sqliteAdapter({
wal: true, // or object with settings
client: { url: process.env.DATABASE_URL },
})
```
| WAL Setting | Default | Notes |
|-------------|---------|-------|
| `journalSizeLimit` | `67108864` (64MB) | WAL file size before checkpointing |
| `synchronous` | `'FULL'` | `EXTRA` / `FULL` / `NORMAL` / `OFF` — perf vs safety tradeoff |
## Cloudflare D1 (Beta)
Serverless SQLite via `@payloadcms/db-d1-sqlite`:
```ts
import { sqliteD1Adapter } from '@payloadcms/db-d1-sqlite'
export default buildConfig({
db: sqliteD1Adapter({
binding: cloudflare.env.D1,
readReplicas: 'first-primary', // optional, experimental
}),
})
```
- Inherits all SQLite adapter options except `client` (uses `binding` instead)
- Read replicas: `'first-primary'` strategy — writes always go to primary
- Must enable read replication in Cloudflare dashboard separately
## Key Takeaways
- SQLite adapter uses Drizzle ORM + libSQL — supports local files, Turso, and Cloudflare D1
- **Dev mode**: `push: true` auto-syncs schema — don't mix with manual migrations
- **Production**: use migrations exclusively (`push: false`)
- Direct Drizzle access via `payload.db.drizzle` — generate types first with `npx payload generate:db-schema`
- `beforeSchemaInit` for adding non-Payload tables (e.g. DB migration); `afterSchemaInit` for extending Payload tables
- Only `rawTables` mutation in `beforeSchemaInit` propagates to generated schema file
- WAL mode (`wal: true`) recommended for concurrent read-heavy workloads
- `blocksAsJSON: true` can improve performance when using many blocks
- `autoIncrement: true` prevents ID reuse — useful for audit/compliance scenarios
- Cloudflare D1 adapter is beta — same API, `binding` instead of `client`
## Sources
- `raw/database__sqlite.md` — official Payload CMS SQLite documentation
## Related
- [[wiki/payloadcms/database-overview|Database — Overview]]
- [[wiki/payloadcms/database-postgres|Database — Postgres]]
- [[wiki/payloadcms/database-mongodb|Database — MongoDB]]
- [[wiki/payloadcms/database-migrations|Database Migrations]]
- [[wiki/payloadcms/database-indexes|Database Indexes]]