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 handling
  • find(id) – Retrieves a record by its identifier
  • findByUid(uid) – Locates records by user identifier
  • findByUserCode(userCode) – Finds records by device flow user code
  • consume(id) – Marks a record as consumed (used for authorization codes)
  • destroy(id) – Permanently removes a record
  • revokeByGrantId(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:

  1. 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 mysql2 for MySQL).

  2. Add the driver to dependencies – Update packages/core/package.json to include your database client library.

  3. Create a custom adapter – Implement the Adapter interface in a new file, providing all seven required methods using your database client's query syntax.

  4. Replace the default adapter – Edit packages/core/src/oidc/init.ts to import and inject your custom adapter into the OIDC provider configuration.

  5. Configure environment variables – Set DB_URL and any additional variables your adapter requires in .env or your CI configuration, as documented in .github/CONTRIBUTING.md.

  6. Run database migrations – Execute your custom schema creation scripts. Note that pnpm cli db seed only supports PostgreSQL; other databases require manual migration.

  7. Start Logto – Launch with pnpm start:dev or 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_URL defined in packages/shared/src/node/env/GlobalValues.ts to configure database connections
  • PostgreSQL users can connect to custom databases by changing the DSN without modifying source code
  • Alternative databases require implementing the oidc-provider Adapter interface with seven required methods: upsert, find, findByUid, findByUserCode, consume, destroy, and revokeByGrantId
  • Integration point for custom adapters is packages/core/src/oidc/init.ts, replacing the default PostgreSQL adapter from packages/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →