payloadcms/payload · critical · Error

Error: cannot connect to SQLite: ${message}

Error message

Error: cannot connect to SQLite: ${message}

What it means

Thrown by the SQLite (db-sqlite) adapter's `connect` after `better-sqlite3` fails to open the database. The adapter logs the message, rejects the init promise, and re-throws a wrapped Error so `payload.init()` rejects.

Source

Thrown at packages/db-sqlite/src/connect.ts:57

    }

    const logger = this.logger || false
    this.drizzle = drizzle(this.client, { logger, schema: this.schema })

    if (!hotReload) {
      if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
        this.payload.logger.info(`---- DROPPING TABLES ----`)
        await this.dropDatabase({ adapter: this })
        this.payload.logger.info('---- DROPPED TABLES ----')
      }
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    this.payload.logger.error({ err, msg: `Error: cannot connect to SQLite: ${message}` })
    if (typeof this.rejectInitializing === 'function') {
      this.rejectInitializing()
    }
    throw new Error(`Error: cannot connect to SQLite: ${message}`)
  }

  // Only push schema if not in production
  if (
    process.env.NODE_ENV !== 'production' &&
    process.env.PAYLOAD_MIGRATING !== 'true' &&
    this.push !== false
  ) {
    await pushDevSchema(this as unknown as DrizzleAdapter)
  }

  if (typeof this.resolveInitializing === 'function') {
    this.resolveInitializing()
  }

  if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
    await this.migrate({ migrations: this.prodMigrations as Migration[] })
  }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Make `dbPath` absolute and confirm its parent directory exists and is writable.
  2. Rebuild the native binding for your Node ABI: `npm rebuild better-sqlite3` (or reinstall).
  3. Ensure no other process holds an exclusive lock on the file.
  4. On serverless/read-only filesystems, place the DB on a writable volume.

Example fix

// before
sqliteAdapter({ client: { url: file:./db.sqlite } }) // relative, dir missing
// after
import { mkdirSync } from 'fs'
mkdirSync('/data', { recursive: true })
sqliteAdapter({ client: { url: 'file:/data/db.sqlite' } })
Defensive patterns

Strategy: validation

Validate before calling

function assertSqliteWritable(dbPath) {
  const dir = path.dirname(path.resolve(dbPath))
  fs.mkdirSync(dir, { recursive: true })
  fs.accessSync(dir, fs.constants.W_OK)
}

Type guard

const isAbsoluteWritablePath = (p) => path.isAbsolute(p)

Try / catch

try { await payload.init() }
catch (e) { if (/cannot connect to SQLite/.test(e.message)) { await rebuildBetterSqlite() } else throw e }

Prevention

When it happens

Trigger: The SQLite file path is unwritable or its directory doesn't exist, the file is locked by another process, the native binding is missing/incompatible (Node ABI mismatch), or the file is corrupt.

Common situations: Relative or wrong `dbPath`; target directory missing or read-only; file permissions; Node version vs better-sqlite3 prebuilt binary mismatch; file on a read-only/serverless filesystem; another process holds an exclusive lock.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/f37ea7dd6a14fb65. Report an issue: GitHub.