How to Connect Logto to a Custom Database: Complete Implementation Guide
Connect Logto to a custom database by either setting the DB_URL environment variable to a PostgreSQL DSN for standard setups, or implementing a custom OIDC storage adapter in packages/core/src/oidc/init.ts to support alternative databases like MySQL or MongoDB.
Logto, the open-source identity platform maintained in the logto-io/logto repository, relies on a relational database for all persistence accessed through the DB_URL environment variable defined in packages/shared/src/node/env/GlobalValues.ts. While the platform defaults to PostgreSQL using the built-in adapter in packages/core/src/oidc/adapter.ts, the architecture supports custom database integrations through the oidc-provider library's adapter pattern. This guide demonstrates both methods to connect Logto to a custom database, from simple DSN configuration to full custom adapter implementations.
Understanding Logto's Database Architecture
Logto stores authentication data, user sessions, and application configurations in a relational database accessed via the DB_URL environment variable. The connection is initialized in packages/shared/src/node/env/GlobalValues.ts, where the databaseUrl value determines the target database endpoint.
By default, Logto uses the PostgreSQL adapter located at packages/core/src/oidc/adapter.ts. This adapter implements the storage interface required by the underlying oidc-provider library, handling OIDC model persistence through standard SQL operations.
Method 1: Connect to a Custom PostgreSQL Instance
The simplest way to connect Logto to a custom database is providing a compatible PostgreSQL connection string. Since the default adapter uses standard PostgreSQL SQL, any PostgreSQL-compatible database (including AWS RDS, Google Cloud SQL, or self-hosted instances) works without code modifications.
Configuring the DB_URL Environment Variable
Locate the environment configuration in your deployment environment. According to the source code in packages/shared/src/node/env/GlobalValues.ts, Logto reads the DB_URL variable to establish database connectivity.
Set the variable to your custom PostgreSQL DSN:
# .env
DB_URL=postgres://myuser:mypassword@my-db-host:5432/mylogto
This approach requires no changes to packages/core/src/oidc/init.ts or the adapter logic, as the default implementation handles PostgreSQL connections automatically.
Method 2: Implement a Custom OIDC Storage Adapter
For databases other than PostgreSQL (such as MySQL, SQLite, or NoSQL stores), you must implement a custom adapter that conforms to the oidc-provider library's interface. This replaces the default PostgreSQL adapter in packages/core/src/oidc/adapter.ts with your database-specific implementation.
Required Adapter Interface Methods
A custom adapter must implement seven core methods to satisfy the OIDC provider contract:
upsert(id, payload, expiresIn)– Creates or updates a record with TTL handlingfind(id)– Retrieves a record by its identifierfindByUid(uid)– Locates records by user identifierfindByUserCode(userCode)– Finds records by device flow user codeconsume(id)– Marks a record as consumed (used for authorization codes)destroy(id)– Permanently removes a recordrevokeByGrantId(grantId)– Bulk deletes records associated with a grant
Custom Adapter Implementation Example
Create a new file at packages/core/src/oidc/custom-adapter.ts with the following skeleton. Replace YourDbClient with your specific database driver:
// packages/core/src/oidc/custom-adapter.ts
import type { Adapter } from 'oidc-provider';
export class CustomAdapter implements Adapter {
constructor(private readonly env: any, private readonly db: YourDbClient) {}
async upsert(id: string, payload: Record<string, unknown>, expiresIn: number) {
await this.db.set(id, { ...payload, expiresAt: Date.now() + expiresIn * 1000 });
}
async find(id: string) {
const item = await this.db.get(id);
if (!item) return undefined;
return { ...item };
}
async findByUid(uid: string) {
const items = await this.db.query({ uid });
return items.length ? items[0] : undefined;
}
async findByUserCode(userCode: string) {
const items = await this.db.query({ userCode });
return items.length ? items[0] : undefined;
}
async consume(id: string) {
const item = await this.db.get(id);
if (item) {
await this.db.set(id, { ...item, consumed: true });
}
}
async destroy(id: string) {
await this.db.delete(id);
}
async revokeByGrantId(grantId: string) {
await this.db.deleteMany({ grantId });
}
}
Wiring the Custom Adapter in init.ts
Modify packages/core/src/oidc/init.ts to instantiate your custom adapter instead of the default PostgreSQL implementation:
// packages/core/src/oidc/init.ts
import { Provider } from 'oidc-provider';
import { CustomAdapter } from './custom-adapter.js';
const provider = new Provider(issuer, {
// ... other options ...
adapter: (modelName) => new CustomAdapter(env, myDbClient).bind(null, modelName),
});
The provider instantiates the adapter per model name (AuthorizationCode, RefreshToken, etc.), passing the specific model context to your implementation.
Step-by-Step Implementation Guide
Follow these steps to connect Logto to your custom database:
-
Set up your database – Ensure the database is network-accessible from the Logto process and install the appropriate Node.js driver (e.g.,
npm install mysql2for MySQL). -
Add the driver to dependencies – Update
packages/core/package.jsonto include your database client library. -
Create a custom adapter – Implement the
Adapterinterface in a new file, providing all seven required methods using your database client's query syntax. -
Replace the default adapter – Edit
packages/core/src/oidc/init.tsto import and inject your custom adapter into the OIDC provider configuration. -
Configure environment variables – Set
DB_URLand any additional variables your adapter requires in.envor your CI configuration, as documented in.github/CONTRIBUTING.md. -
Run database migrations – Execute your custom schema creation scripts. Note that
pnpm cli db seedonly supports PostgreSQL; other databases require manual migration. -
Start Logto – Launch with
pnpm start:devor your Docker Compose setup. Verify connectivity by inspecting database logs or Logto's application logs.
MySQL Adapter Example
For MySQL-specific implementations, replace the generic store logic with SQL queries:
import type { Adapter } from 'oidc-provider';
import mysql from 'mysql2/promise';
export class MySQLAdapter implements Adapter {
private pool = mysql.createPool({ uri: process.env.DB_URL });
async upsert(id: string, payload: Record<string, unknown>, expiresIn: number) {
const sql = `INSERT INTO oidc_storage (id, data, expires_at)
VALUES (?, ?, NOW() + INTERVAL ? SECOND)
ON DUPLICATE KEY UPDATE data = VALUES(data), expires_at = VALUES(expires_at)`;
await this.pool.execute(sql, [id, JSON.stringify(payload), expiresIn]);
}
async find(id: string) {
const [rows] = await this.pool.execute('SELECT * FROM oidc_storage WHERE id = ?', [id]);
return rows[0] ? JSON.parse(rows[0].data) : undefined;
}
// Implement remaining methods (findByUid, findByUserCode, consume, destroy, revokeByGrantId) similarly...
}
Register this adapter in packages/core/src/oidc/init.ts by passing the class to the provider's adapter option:
adapter: MySQLAdapter, // provider will instantiate it per model name
Summary
- Logto uses
DB_URLdefined inpackages/shared/src/node/env/GlobalValues.tsto configure database connections - PostgreSQL users can connect to custom databases by changing the DSN without modifying source code
- Alternative databases require implementing the
oidc-providerAdapter interface with seven required methods:upsert,find,findByUid,findByUserCode,consume,destroy, andrevokeByGrantId - Integration point for custom adapters is
packages/core/src/oidc/init.ts, replacing the default PostgreSQL adapter frompackages/core/src/oidc/adapter.ts - Migration scripts (
pnpm cli db seed) are PostgreSQL-specific; custom databases require manual schema management
Frequently Asked Questions
Can I use MySQL or MongoDB with Logto instead of PostgreSQL?
Yes, but you must implement a custom OIDC storage adapter. Logto's default adapter in packages/core/src/oidc/adapter.ts only supports PostgreSQL. For MySQL, MongoDB, or other databases, create a class implementing the Adapter interface from oidc-provider with the seven required methods, then wire it into packages/core/src/oidc/init.ts.
What environment variable configures the database connection?
The DB_URL environment variable controls the database connection string. This variable is defined in packages/shared/src/node/env/GlobalValues.ts and documented in .github/CONTRIBUTING.md. For custom PostgreSQL instances, simply update this value. For completely custom adapters, you may use DB_URL or define additional environment variables as needed by your implementation.
Do I need to modify the Logto source code to use a custom database?
For PostgreSQL-compatible databases, no code changes are required—only the DB_URL environment variable needs updating. For non-PostgreSQL databases, you must modify packages/core/src/oidc/init.ts to import and register your custom adapter implementation, replacing the default PostgreSQL adapter reference.
Will Logto's built-in migration scripts work with my custom database?
No. The pnpm cli db seed command and other built-in migrations are designed specifically for PostgreSQL schemas. If you implement a custom adapter for MySQL, MongoDB, or another database, you must create and run your own migration scripts to establish the required tables or collections before starting Logto.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →