# How to Configure Cloudflare D1 Database Bindings with Next.js on Workers

> Learn to configure Cloudflare D1 database bindings with Next.js on Workers. Declare your database, generate TypeScript types, and access it at runtime for seamless data integration.

- Repository: [Muhammad Arifin/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare)
- Tags: how-to-guide
- Published: 2026-03-03

---

**To configure Cloudflare D1 database bindings with Next.js on Workers, declare the database in `wrangler.jsonc`, generate TypeScript definitions with `cf-typegen`, and access the binding at runtime via `getCloudflareContext()` from `@opennextjs/cloudflare`.**

This guide walks through the exact implementation used in the [ifindev/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare) repository, where Next.js runs on Cloudflare Workers using OpenNext. The setup enables type-safe SQLite queries at the edge through Drizzle-ORM.

## Understanding the Binding Architecture

Cloudflare Workers receive an **`env`** object containing all bound resources. When you deploy a Next.js app to Workers, the runtime injects D1 database instances into this object based on your configuration.

### How D1 Bindings Work

The **D1 binding** creates a named reference that becomes available as `env.<binding_name>` inside your worker. In `wrangler.jsonc`, the `d1_databases` array maps binding names to actual database IDs. At runtime, the `env` object provided by `getCloudflareContext()` carries this binding, allowing direct SQL execution without network round-trips to external database servers.

### Type-Safe Access Pattern

The repository implements a helper pattern in [`src/db/index.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/db/index.ts) that wraps the raw binding with Drizzle-ORM. This abstraction ensures every database interaction is fully typed while hiding the complexity of context retrieval from your business logic.

## Step-by-Step Configuration

### 1. Create the D1 Database

First, provision the database using the Wrangler CLI:

```bash
wrangler d1 create your-app-name

# Copy the generated database_id for the next step

```

This command returns a UUID that uniquely identifies your database in Cloudflare's infrastructure.

### 2. Declare the Binding in wrangler.jsonc

Open `wrangler.jsonc` and add the database to the `d1_databases` array. The `binding` property defines the JavaScript identifier you'll use to access the database:

```jsonc
// wrangler.jsonc
{
  "name": "next-cf-app",
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-03-01",
  "d1_databases": [
    {
      "binding": "next_cf_app",
      "database_name": "next-cf-app",
      "database_id": "757a32d1-5779-4f09-bcf3-b268013395d4",
      "migrations_dir": "./src/drizzle"
    }
  ]
}

```

The `migrations_dir` field tells Wrangler where to find Drizzle schema files for local development.

### 3. Generate TypeScript Definitions

Run the type generation command to create compile-time safety for your bindings:

```bash
pnpm run cf-typegen

```

This produces [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts), which declares `next_cf_app: D1Database` (or whichever binding name you chose) on the `env` interface. Without this step, TypeScript treats the binding as `any`, eliminating intellisense and type checking.

### 4. Create the Database Helper

Create or verify [`src/db/index.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/db/index.ts) contains the following factory function:

```typescript
// src/db/index.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

export async function getDb() {
  const { env } = await getCloudflareContext();
  // The property name must match the binding in wrangler.jsonc
  return drizzle(env.next_cf_app, { schema });
}

export * from "./schema";

```

This function must be called from within worker-bound code (API routes, server actions, or React Server Components in the Edge runtime). The `getCloudflareContext()` call throws if executed outside the Worker environment, such as in standard Node.js tests.

### 5. Apply Database Migrations

Before querying data, push your schema to the D1 instance:

```bash
pnpm run db:generate    # Generate migration SQL from schema

pnpm run db:migrate:local  # Apply to local D1 during development

```

## Querying D1 in Application Code

With the helper established, any server-side module can access the database by importing `getDb()`:

```typescript
// src/modules/todos/actions/get-todos.action.ts
import { getDb, todos } from "@/db";
import { eq } from "drizzle-orm";

export async function getTodos(userId: string) {
  const db = await getDb();
  return db.select()
    .from(todos)
    .where(eq(todos.user_id, userId))
    .all();
}

```

The `env.next_cf_app` binding resolves automatically whether running locally via `wrangler dev` or in production on Cloudflare's edge network.

## Local Development Workflow

Cloudflare's local development server automatically provisions a local D1 instance when you run:

```bash
pnpm run wrangler:dev

```

This starts the Worker runtime with the D1 binding available at `env.next_cf_app`. For the full Next.js experience with Hot Module Replacement, run the standard Next.js dev server alongside it:

```bash

# Terminal 1

pnpm run wrangler:dev

# Terminal 2

pnpm run dev

```

Your application at `http://localhost:3000` now reads and writes to the local D1 database through the configured binding.

## Troubleshooting Common Configuration Issues

| Issue | Solution |
|-------|----------|
| **Binding name mismatch** | Verify the string in `env.<binding>` inside [`src/db/index.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/db/index.ts) exactly matches the `"binding"` value in `wrangler.jsonc`. |
| **Missing TypeScript types** | If `env.next_cf_app` shows as `any`, re-run `pnpm run cf-typegen` to regenerate [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts). |
| **Context unavailable** | Ensure `getDb()` is called inside async functions running on the Worker. It cannot run in static generation or pure Node contexts. |
| **Database not found** | Confirm the `database_id` in `wrangler.jsonc` matches the ID returned from `wrangler d1 create`. |

## Summary

- **Declare bindings** in `wrangler.jsonc` using the `d1_databases` array with a unique binding name and database ID.
- **Generate types** via `pnpm run cf-typegen` to enable IDE autocomplete and compile-time safety for `env` properties.
- **Access the binding** through `getCloudflareContext()` from `@opennextjs/cloudflare`, available exclusively in Worker runtime contexts.
- **Abstract the connection** in [`src/db/index.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/db/index.ts) using Drizzle-ORM to provide clean, type-safe database access across your Next.js application.
- **Develop locally** using `wrangler dev`, which automatically creates a local D1 instance bound to your worker.

## Frequently Asked Questions

### How do I change the D1 database binding name?

Modify the `"binding"` property in `wrangler.jsonc` to your preferred identifier (e.g., `"DB"` or `"primary_db"`), then update [`src/db/index.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/db/index.ts) to reference the same name on the `env` object (e.g., `env.DB`). Run `pnpm run cf-typegen` afterward to update TypeScript definitions.

### Can I use multiple D1 databases in the same Next.js app?

Yes. Add multiple objects to the `d1_databases` array in `wrangler.jsonc`, each with a unique `binding` name. Create separate helper functions or modify `getDb()` to accept a binding name parameter, passing the appropriate `env.<binding>` to Drizzle.

### Why does getCloudflareContext() throw an error in my tests?

`getCloudflareContext()` requires the Cloudflare Worker runtime environment. It will throw if called outside a Worker context, such as in Jest tests running in Node.js or during Next.js static site generation. Use integration tests with `wrangler dev` or mock the context for unit testing.

### What is the relationship between wrangler.jsonc and the env object?

`wrangler.jsonc` serves as the configuration manifest. When Wrangler deploys or runs your worker, it reads this file and injects the specified resources into the `env` object available at runtime. The `binding` field in the JSON becomes the property name on `env` that holds the actual D1Database instance.