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

> Understand Hyperdrive mode vs D1 mode for the Postgres client in Open SEO. Hyperdrive uses live Postgres bindings for production, D1 defaults to SQLite/D1. Learn the key differences.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-12

---

**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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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:**

```typescript
// 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:**

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/worker-configuration.d.ts)** (lines 9685-9722): Defines the `Hyperdrive` interface with connection credentials【L9685-L9722】
- **[`src/db/provider.ts`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/src/db/provider.ts) handles the branching logic automatically.