Skip to main content
theAuth has two separate storage decisions, and people often mix them up.
  1. The database holds users, sessions, agents, delegations and the audit log. It has to be durable. You choose it with database.provider.
  2. Secondary storage holds small, short-lived, high-churn values: rate-limit counters, device login codes. It only needs fast reads, writes and an atomic counter. You choose it with secondaryStorage.
The default for the second one is process memory, which is correct on a laptop and quietly wrong almost everywhere else. Most of the “it works locally” problems in Troubleshooting come from that default.

Start from where you deploy

The database, in one paragraph each

sqlite (sql.js). SQLite compiled to WebAssembly. No native build, runs on Node, Bun, Deno and edge runtimes. The whole database lives in memory and the file is rewritten after each write. Good for development, tests and tiny single-process apps. Two processes on one file will overwrite each other, and a read-only or ephemeral filesystem loses the data. sqlite-native (better-sqlite3). Real SQLite on disk, fast, one process at a time in practice. Needs a native build (install better-sqlite3 yourself). A good fit for a single VPS where you want zero database operations. Back up the file. postgres. The default recommendation for anything with more than one instance or any real traffic. Needs the pg package. Use a pooler on serverless. mysql. Choose it when MySQL is what your team already runs. Needs mysql2. Behavior is the same as Postgres for theAuth’s purposes. d1. Cloudflare D1 through a Worker binding. Use it when the app lives on Workers. Pass the binding instead of a URL. Setup snippets for each are on Database setup.

Secondary storage, in one paragraph each

The "database" backend creates one table, theauth_secondary_storage, and increments counters with a single guarded UPDATE, so it is exact on every supported database. Expired rows are removed lazily; run databaseStorage(db).purgeExpired() from a cron if you have a large keyspace. You can choose per feature. A common production setup on Workers is exact storage for device codes and soft storage for rate limits:
More detail, including the interface for a custom store, is on Secondary storage.

A few rules of thumb

  • If you are unsure, use Postgres plus secondaryStorage: 'database'. It has one moving part and works on every deployment shape except Workers.
  • Do not use memory for anything you would be upset to lose or to count twice: device codes and rate limits on a deployment with more than one instance.
  • Do not put the main database on KV. KV is only an option for secondary storage.
  • Moving from sqlite to Postgres later is a data copy. The table names are the same (theauth_*), so plan a one-off export and import, and run createTheAuth against the new database first so the tables exist.
  • The Prisma adapter reads and writes theAuth tables from code that already uses Prisma. It does not replace the choice above. See Prisma.
Using the Go library instead? Its backends are different, see Storage backends and Shared state for several replicas.

Database setup

Connection snippets for every provider.

Secondary storage

Interface, adapters and per-feature overrides.

Production checklist

Check storage and everything else before launch.

Troubleshooting

Serverless and edge gotchas.
Last modified on October 9, 2026