Hyperdrive Mode vs D1 Mode for the Postgres Client in Open SEO

Hyperdrive mode creates a live, per-request Postgres client using Cloudflare's Hyperdrive binding for production deployments, while D1 mode bypasses Postgres entirely and defaults to SQLite/D1 storage with a no-op fallback.

The every-app/open-seo repository abstracts database access through two distinct operational paths for its Postgres client. Understanding whether to use Hyperdrive mode (managed Postgres over TCP) or D1 mode (the default SQLite fallback) is critical for configuring production workloads versus local development environments.

How Hyperdrive Mode Works

The Hyperdrive Binding Configuration

Hyperdrive mode relies on the Cloudflare Hyperdrive binding defined in worker-configuration.d.ts. This interface exposes connection parameters—including host, username, password, and database name—required to establish a TCP socket to your Postgres instance. According to the every-app/open-seo source, the binding structure spans lines 9685 through 9722 in worker-configuration.d.ts【L9685-L9722】.

Per-Request Client Creation

When DATABASE_PROVIDER is set to postgres, the system activates request-scoped client creation in src/db/pg/client.ts. The implementation uses AsyncLocalStorage to maintain isolation between concurrent requests, creating a fresh postgres client for each invocation using the Hyperdrive connection string. To accommodate Cloudflare Workers' socket reclaim behavior, the connection pool is explicitly limited to a single connection (max: 1). Attempting to access the database outside a request scope throws a specific error defined at lines 36-48 of the same file【L36-L48】.

How D1 Mode Works (The No-Op Fallback)

Provider Selection Logic

D1 mode serves as the default database path when DATABASE_PROVIDER is not explicitly set to postgres. The selection logic resides in src/db/provider.ts (lines 6-35), where the application checks for the presence of a Hyperdrive binding【L6-L35】. If the provider configuration does not match postgres, the system initializes the D1/SQLite path instead.

Behavior Without Postgres

In D1 mode, the withPostgres wrapper function becomes a no-op passthrough. Rather than instantiating a Postgres client, the wrapper simply executes the supplied function (fn) directly without any database context. This design allows the same codebase to run seamlessly against Cloudflare D1 (SQLite) or bypass Postgres entirely during testing, eliminating connection overhead when Postgres capabilities are not required.

Key Differences Between Hyperdrive and D1 Modes

Feature Hyperdrive Mode D1 Mode
Activation Trigger DATABASE_PROVIDER=postgres environment variable set Default fallback when DATABASE_PROVIDER is unset or set to d1
Client Instantiation Creates per-request postgres client via Hyperdrive binding No client created; pure passthrough execution
Connection Pooling Single connection limit (max: 1) via Hyperdrive TCP socket No network connections established
Error Handling Throws "Postgres database accessed outside a request scope" if used incorrectly No Postgres-specific errors possible
Production Use Designed for scalable, durable Postgres storage on Cloudflare Workers Suitable for local development, testing, or D1-only deployments

Practical Code Examples

Hyperdrive Mode Usage:

// src/server/workflows/pgStep.ts pattern
import { withPostgres } from "@/db";

export async function pgStep<T>(step: WorkflowStep<T>, name: string, fn: () => Promise<T>) {
  return await withPostgres(async () => {
    // Executes with a live Postgres client scoped to this request
    return await fn();
  });
}

D1 Mode Behavior:

// When DATABASE_PROVIDER is not 'postgres', withPostgres is a no-op
import { withPostgres } from "@/db";

await withPostgres(() => {
  // Function runs immediately without Postgres client initialization
  // Suitable for D1/SQLite operations or unit testing
});

Critical Source Files

  • worker-configuration.d.ts (lines 9685-9722): Defines the Hyperdrive interface with connection credentials【L9685-L9722】
  • src/db/provider.ts (lines 6-35): Contains the DATABASE_PROVIDER selection logic that determines which mode activates【L6-L35】
  • src/db/pg/client.ts (lines 36-48): Implements request-scoped storage and the error thrown when accessing Postgres outside a valid scope【L36-L48】

Summary

  • Hyperdrive mode provides a production-ready, per-request Postgres client using Cloudflare's Hyperdrive TCP binding, requiring DATABASE_PROVIDER=postgres and proper binding configuration.
  • D1 mode acts as a zero-overhead fallback that bypasses Postgres client creation entirely, defaulting to SQLite/D1 behavior or simple passthrough execution.
  • The modes are mutually exclusive based on the DATABASE_PROVIDER environment variable checked in src/db/provider.ts.
  • Hyperdrive connections are explicitly pooled with max: 1 to handle Workers' socket lifecycle, while D1 mode eliminates connection concerns completely.

Frequently Asked Questions

What happens if I try to use Postgres outside a request scope in Hyperdrive mode?

The application throws a clear runtime error: "Postgres database accessed outside a request scope". This safety mechanism, implemented in src/db/pg/client.ts, prevents unscoped database access that would lack proper connection management or isolation.

Can I use Hyperdrive mode in local development?

Yes, but you must provide a Hyperdrive binding or a local connection string that mimics the Hyperdrive interface. Without the binding defined in worker-configuration.d.ts, the application will fallback to D1 mode.

Why does D1 mode act as a no-op for Postgres operations?

D1 mode is designed for deployments using Cloudflare's D1 (SQLite) or environments without Postgres infrastructure. By making withPostgres a simple function wrapper that immediately executes fn, the codebase avoids instantiating connection pools or TCP sockets that would fail without a Hyperdrive configuration.

How do I switch between Hyperdrive and D1 modes?

Toggle the DATABASE_PROVIDER environment variable. Set it to postgres to enable Hyperdrive mode and create per-request clients, or omit it/set it to d1 to activate the default D1 mode. The provider check in src/db/provider.ts handles the branching logic automatically.

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 →