PHPackages                             ntanduy/cloudflare-d1-database - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. ntanduy/cloudflare-d1-database

ActiveLibrary[Database &amp; ORM](/categories/database)

ntanduy/cloudflare-d1-database
==============================

Cloudflare D1 database driver for Laravel — full Eloquent &amp; Query Builder support.

v0.11.0(1mo ago)267.8k↑419.4%7[1 PRs](https://github.com/TanDuy03/cloudflare-d1-database/pulls)MITPHPPHP ^8.2CI passing

Since Aug 22Pushed 2w ago1 watchersCompare

[ Source](https://github.com/TanDuy03/cloudflare-d1-database)[ Packagist](https://packagist.org/packages/ntanduy/cloudflare-d1-database)[ Docs](https://github.com/TanDuy03/cloudflare-d1-database)[ GitHub Sponsors](https://github.com/TanDuy03)[ RSS](/packages/ntanduy-cloudflare-d1-database/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (10)Dependencies (35)Versions (37)Used By (0)

Cloudflare D1 Database Driver for Laravel
=========================================

[](#cloudflare-d1-database-driver-for-laravel)

[![codecov](https://camo.githubusercontent.com/2b7452b7913321aee4427325964d781a97db7e7ba72ad6a12b18eb0a1a2c462c/68747470733a2f2f636f6465636f762e696f2f67682f54616e44757930332f636c6f7564666c6172652d64312d64617461626173652f67726170682f62616467652e7376673f746f6b656e3d394d534a3532375a4d58)](https://codecov.io/gh/TanDuy03/cloudflare-d1-database)[![Tests](https://github.com/TanDuy03/cloudflare-d1-database/actions/workflows/tests.yml/badge.svg)](https://github.com/TanDuy03/cloudflare-d1-database/actions/workflows/tests.yml)[![PHP](https://camo.githubusercontent.com/c4592483be21a67431df3607bb00d9aa91ffb5c78a669a30d52ae5acded2f96a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322b2d3737374242343f6c6f676f3d706870)](https://camo.githubusercontent.com/c4592483be21a67431df3607bb00d9aa91ffb5c78a669a30d52ae5acded2f96a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322b2d3737374242343f6c6f676f3d706870)[![Laravel](https://camo.githubusercontent.com/ddefc58b81fa32b908330dba148de958785552433a949c7eb425b6fde50a6504/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302d2d31332e782d4646324432303f6c6f676f3d6c61726176656c)](https://camo.githubusercontent.com/ddefc58b81fa32b908330dba148de958785552433a949c7eb425b6fde50a6504/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302d2d31332e782d4646324432303f6c6f676f3d6c61726176656c)[![Latest Stable Version](https://camo.githubusercontent.com/d628514d150e0d04ac534664ba47e5de5f6ab157d94bd1372c2b9a38d4c742e4/68747470733a2f2f706f7365722e707567782e6f72672f6e74616e6475792f636c6f7564666c6172652d64312d64617461626173652f762f737461626c65)](https://packagist.org/packages/ntanduy/cloudflare-d1-database)[![Total Downloads](https://camo.githubusercontent.com/05001ba41a834f8b20846c8d371ffda237a6c7afdf6cff5c50a202b126c8059e/68747470733a2f2f706f7365722e707567782e6f72672f6e74616e6475792f636c6f7564666c6172652d64312d64617461626173652f646f776e6c6f616473)](https://packagist.org/packages/ntanduy/cloudflare-d1-database)[![Monthly Downloads](https://camo.githubusercontent.com/02987b299cb6c9466843887b4c519c5ab2c2583fcfdd531eccf618fa51e26607/68747470733a2f2f706f7365722e707567782e6f72672f6e74616e6475792f636c6f7564666c6172652d64312d64617461626173652f642f6d6f6e74686c79)](https://packagist.org/packages/ntanduy/cloudflare-d1-database)[![License](https://camo.githubusercontent.com/b40ca42b8d6f352d6887276e2dfc79099a4516ef3c1ac067bb9769352b7cbd66/68747470733a2f2f706f7365722e707567782e6f72672f6e74616e6475792f636c6f7564666c6172652d64312d64617461626173652f6c6963656e7365)](https://packagist.org/packages/ntanduy/cloudflare-d1-database)

Use [Cloudflare D1](https://developers.cloudflare.com/d1) as a native Laravel database driver — full Eloquent ORM, Query Builder, and Migration support.

🎯 Requirements
--------------

[](#-requirements)

- **PHP**: &gt;= 8.2
- **Laravel**: 10.x, 11.x, 12.x, or 13.x

✨ Features
----------

[](#-features)

- **Full Laravel Integration** — Eloquent ORM, Query Builder, Migrations, Seeding
- **Two Connection Drivers** — REST API (zero infrastructure) or Worker (low latency)
- **Batch Queries** — Execute multiple statements in a single HTTP round-trip
- **Bulk Insert** — Insert hundreds of rows efficiently via D1 batch execution
- **Sessions / Read Replication** — Leverage D1 global read replicas for lower-latency reads (Worker driver)
- **Auto Read/Write Splitting** — Automatic routing of SELECTs to replicas and writes to primary (Worker driver)
- **Import** — Import SQL files into D1 via `php artisan d1:import`
- **Schema Dump** — Export your D1 database via `php artisan d1:schema-dump`
- **Time Travel** — Point-in-time recovery via `php artisan d1:time-travel`
- **Database Info** — Inspect your D1 database with `php artisan d1:info`
- **Circuit Breaker** — Fail fast on sustained outages instead of blocking on retries
- **Automatic Retries** — Exponential backoff with jitter for 5xx/429 errors
- **Query Logging** — Optional callback for monitoring and debugging
- **Health Check** — Built-in `php artisan d1:health` to verify connection and measure latency

🚀 Installation
--------------

[](#-installation)

```
composer require ntanduy/cloudflare-d1-database
```

👏 Usage
-------

[](#-usage)

### Step 1: Publish Configuration

[](#step-1-publish-configuration)

```
php artisan vendor:publish --tag="d1-config"
```

This creates `config/d1-database.php` with all available options.

### Step 2: Choose a Driver

[](#step-2-choose-a-driver)

This package supports two drivers to connect Laravel with Cloudflare D1:

DriverHow it worksLatencySetup**REST** (default)Calls [Cloudflare D1 REST API](https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/query/) directlyHigher (extra HTTP hop)API Token only**Worker**Routes queries through your own [Cloudflare Worker](https://developers.cloudflare.com/workers/)Lower (co-located with D1)Requires deploying a Worker---

### Driver 1: REST API (Default)

[](#driver-1-rest-api-default)

The simplest setup — no extra infrastructure needed. Queries are sent to Cloudflare's REST API.

**Add to your `.env`:**

```
CF_D1_API_TOKEN=your_api_token
CF_D1_ACCOUNT_ID=your_account_id
CF_D1_DATABASE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```

**How to get these values:**

1. **API Token** — Go to [Cloudflare Dashboard → API Tokens](https://dash.cloudflare.com/profile/api-tokens) → Create Token → use the "Edit Cloudflare D1" template
2. **Account ID** — Found on your Cloudflare Dashboard overview page (right sidebar)
3. **Database ID** — Go to [Workers &amp; Pages → D1](https://dash.cloudflare.com/?to=/:account/workers/d1) → click your database → copy the Database ID

That's it! Your Laravel app can now use D1.

---

### Driver 2: Worker (Low Latency)

[](#driver-2-worker-low-latency)

For production apps that need lower latency, deploy a Cloudflare Worker as a proxy between Laravel and D1.

**Add to your `.env`:**

```
CF_D1_DRIVER=worker
CF_D1_WORKER_URL=https://your-d1-worker.your-subdomain.workers.dev
CF_D1_WORKER_SECRET=a-strong-shared-secret
```

#### Deploy the Worker

[](#deploy-the-worker)

A ready-to-deploy Worker template is included in the [`Worker/`](Worker/) directory. To deploy:

```
cd Worker
npm install
npx wrangler secret put WORKER_SECRET
npm run deploy
```

Before deploying, update `wrangler.jsonc` with your D1 database binding:

```
name = "ntanduy-d1-worker"
main = "src/index.ts"
compatibility_date = "2026-03-10"

[[d1_databases]]
binding = "DB"
database_name = "your-database-name"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```

> **Important:** Set `WORKER_SECRET` using `npx wrangler secret put WORKER_SECRET` — never put secrets in `wrangler.jsonc`. This secret must match the `CF_D1_WORKER_SECRET` in your Laravel `.env`.

#### HMAC Request Signing (Optional)

[](#hmac-request-signing-optional)

For additional security, enable HMAC request signing. Each request gets a unique signature that prevents body tampering and provides per-isolate replay detection.

**Laravel `.env`:**

```
CF_D1_HMAC=true
```

**Worker (optional enforcement):**

```
npx wrangler secret put HMAC_REQUIRED        # Set to "true" to reject unsigned requests
npx wrangler secret put HMAC_WINDOW_SECONDS  # Replay window (default: 300 = 5 minutes)
```

When enabled, the PHP driver adds three headers to every request:

- `X-D1-Timestamp` — current Unix timestamp
- `X-D1-Nonce` — random 32-character hex string (unique per request)
- `X-D1-Signature` — HMAC-SHA256 of `timestamp.nonce.body` using the shared secret

The Worker verifies these when present, rejects expired timestamps, and tracks seen nonces to prevent replay within the same isolate. The per-request nonce ensures that two identical requests within the same second produce different signatures and are not falsely rejected as replays. Without `HMAC_REQUIRED=true`, unsigned requests still work (backward compatible).

> **Note:** Replay detection is per-isolate — it resets on Worker cold starts and is separate per Cloudflare colo. For stricter guarantees, consider using Durable Objects for global nonce storage.

#### Worker Endpoints

[](#worker-endpoints)

The Worker exposes these endpoints:

EndpointMethodAuthDescription`/health`GET❌Health check`/query`POST✅ BearerExecute a single SQL query`/batch`POST✅ BearerExecute multiple statements atomically`/exec`POST✅ BearerExecute raw DDL/migration SQL`/raw`POST✅ BearerExecute a query and return raw array-of-arrays---

### Step 3: Set as Default Connection

[](#step-3-set-as-default-connection)

To use D1 as the default database, add to your `.env`:

```
DB_CONNECTION=d1
```

### Step 4: Verify Connection

[](#step-4-verify-connection)

Run the built-in health check to verify your setup:

```
php artisan d1:health
```

```
  D1 Health Check
  Connection : d1
  Driver     : worker

+-------------------------+---------+------------------------------------------+
| Check                   | Status  | Detail                                   |
+-------------------------+---------+------------------------------------------+
| worker_url configured   | ✓ OK    | https://d1-proxy.name.workers.dev        |
| worker_secret configured| ✓ OK    | ******cret                               |
| Query test passed       | ✓ OK    | SELECT 1 as ok                           |
| End-to-end latency      | ✓ OK    | 24 ms                                    |
+-------------------------+---------+------------------------------------------+

  Overall: HEALTHY ✓

```

### Step 5: Run Migrations

[](#step-5-run-migrations)

```
php artisan migrate --database=d1
```

📖 Examples
----------

[](#-examples)

### Eloquent ORM

[](#eloquent-orm)

```
use App\Models\Post;

// Create
$post = Post::create([
    'title' => 'Hello from D1',
    'body' => 'This is stored in Cloudflare D1!',
]);

// Read
$posts = Post::where('published', true)->orderBy('created_at', 'desc')->get();

// Update
$post->update(['title' => 'Updated Title']);

// Delete
$post->delete();
```

### Query Builder

[](#query-builder)

```
use Illuminate\Support\Facades\DB;

// Select
$users = DB::connection('d1')->table('users')
    ->where('active', true)
    ->limit(10)
    ->get();

// Insert
DB::connection('d1')->table('users')->insert([
    'name' => 'John Doe',
    'email' => 'john@example.com',
]);

// Raw queries
$results = DB::connection('d1')->select('SELECT * FROM users WHERE id = ?', [1]);
```

### Query Logger

[](#query-logger)

Monitor queries for debugging or performance analysis:

```
use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->d1()->setQueryLogger(function (
    string $query,
    array $params,
    float $timeMs,
    bool $success,
    ?array $error
) {
    if (! $success) {
        Log::error("D1 query failed: {$query}", [
            'params' => $params,
            'error' => $error,
            'time_ms' => $timeMs,
        ]);
    }
});
```

### Runtime Driver Detection

[](#runtime-driver-detection)

```
use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->getDriver();      // 'rest' or 'worker'
$connection->isWorkerDriver(); // true or false
```

### Batch Queries

[](#batch-queries)

Execute multiple SQL statements in a single HTTP round-trip. On the Worker driver, this uses D1's native `batch()` for atomic execution.

```
use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$results = $connection->batch([
    ['sql' => 'SELECT * FROM users WHERE id = ?', 'params' => [1]],
    ['sql' => 'UPDATE stats SET views = views + 1 WHERE id = ?', 'params' => [5]],
    ['sql' => 'SELECT COUNT(*) as total FROM posts'],
]);

$user  = $results[0]; // Result set from first statement
$stats = $results[1]; // Result set from second statement
$count = $results[2]; // Result set from third statement
```

If any statement fails, a `D1BatchException` is thrown with the index of the failing statement:

```
use Ntanduy\CFD1\D1\Exceptions\D1BatchException;

try {
    $results = $connection->batch($statements);
} catch (D1BatchException $e) {
    // $e->getMessage() includes the failing statement index
}
```

### Driver Feature Matrix

[](#driver-feature-matrix)

FeatureRESTWorkerBulk Insert✅✅Sessions / Read Replication❌ Not supported✅ Full supportAuto Read/Write Splitting❌ Not supported✅ Full supportImport (`d1:import`)✅✅ (via REST credentials)Time Travel (`d1:time-travel`)✅✅ (via REST credentials)Schema Dump✅✅ (via REST credentials)Database Info (`d1:info`)✅ Full metadata✅ Query test + REST metadataBatch Queries✅✅Circuit Breaker✅✅Automatic Retries✅✅### Bulk Insert

[](#bulk-insert)

Insert multiple rows efficiently using D1 batch execution:

```
use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

$connection->bulkInsert('users', [
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob', 'email' => 'bob@example.com'],
    ['name' => 'Charlie', 'email' => 'charlie@example.com'],
]);
```

- Each row becomes a parameterized INSERT (SQL injection safe)
- Each chunk of up to 100 rows is sent as a D1 batch (atomic per chunk — if any statement in a chunk fails, that chunk is rolled back)
- Datasets exceeding D1's 100-statement batch limit are automatically chunked into multiple HTTP calls — **earlier chunks are committed even if a later chunk fails**
- Works with both REST and Worker drivers

### Sessions / Read Replication (Worker Driver Only)

[](#sessions--read-replication-worker-driver-only)

D1 supports [global read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/) — read queries can be served by nearby replicas for lower latency. The Sessions API ensures sequential consistency across queries.

> **Important:** Sessions are only available with the **Worker driver**. The REST API does not support D1 Sessions — this is a [Cloudflare platform limitation](https://developers.cloudflare.com/d1/best-practices/read-replication/).

#### Enable via Config

[](#enable-via-config)

Add to your `.env`:

```
CF_D1_SESSION_ENABLED=true
CF_D1_SESSION_MODE=first-unconstrained   # or 'first-primary'
```

This automatically enables sessions for all queries on the Worker driver.

#### Enable Programmatically

[](#enable-programmatically)

```
use Ntanduy\CFD1\D1\D1Connection;

/** @var D1Connection $connection */
$connection = DB::connection('d1');

// Start a session — first query goes to any instance (fastest)
$connection->withSession('first-unconstrained');

// Or start with the latest data from primary
$connection->withSession('first-primary');

// Execute queries — bookmarks are tracked automatically
$users = DB::table('users')->get();
$posts = DB::table('posts')->get();

// Get the current bookmark (for passing to another request/session)
$bookmark = $connection->getBookmark();

// Start a new session from a previous bookmark
$connection->withSession($bookmark);

// End the session when done
$connection->endSession();
```

#### Session Modes

[](#session-modes)

ModeFirst QueryUse When`first-unconstrained`Any instance (primary or replica)Lowest latency, eventual consistency OK`first-primary`Primary databaseNeed the latest data for first query``At least as fresh as the bookmarkContinuing from a previous session#### How It Works

[](#how-it-works)

1. PHP sends a `session` parameter with each query to the Worker
2. Worker calls `env.DB.withSession(param)` to create a D1 session
3. Worker returns a `bookmark` in the response
4. PHP stores the bookmark and uses it for the next query
5. This ensures **sequential consistency** across HTTP calls

#### Worker Template

[](#worker-template)

The Worker template in [`Worker/`](Worker/) already includes session support. If you're upgrading from a previous version, redeploy the Worker:

```
cd Worker && npm run deploy
```

### Auto Read/Write Splitting (Worker Driver Only)

[](#auto-readwrite-splitting-worker-driver-only)

Automatically route `SELECT` queries to D1 replicas and `INSERT`/`UPDATE`/`DELETE` to the primary — zero code changes required.

```
// config/database.php
'd1' => [
    'driver' => 'd1',
    'd1_driver' => 'worker',
    'worker_url' => env('CF_D1_WORKER_URL'),
    'worker_secret' => env('CF_D1_WORKER_SECRET'),

    'read' => [
        'session' => ['mode' => 'first-unconstrained'],
    ],
    'write' => [
        'session' => ['mode' => 'first-primary'],
    ],
    'sticky' => true,  // After write, reads use primary for consistency
],
```

Once configured, Laravel handles everything:

```
// Automatically goes to replica (fast, nearby)
$users = User::all();

// Automatically goes to primary
User::create(['name' => 'Alice', 'email' => 'alice@example.com']);

// With sticky=true, this read goes to primary (sees the new user)
$user = User::where('email', 'alice@example.com')->first();
```

- **`sticky` (default: `true`)** — after a write, subsequent reads in the same request use the write connector's bookmark for sequential consistency
- Works alongside manual `withSession()` — R/W splitting handles the base routing, you can still use sessions for fine-grained control
- **REST driver ignores** `read`/`write` config — no sessions support, all queries go to primary

### Database Info

[](#database-info)

Inspect your D1 database metadata and connection status:

```
php artisan d1:info
```

Displays database name, UUID, size, table count, read replication mode, R/W splitting status, circuit breaker state, and runs a query test.

```
# Specify a connection
php artisan d1:info --connection=d1
```

> Uses the [D1 REST API](https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/get/) for metadata. Worker-only users see table count and query test but need REST credentials for full metadata.

### Import

[](#import)

Import a SQL file into your D1 database:

```
php artisan d1:import path/to/file.sql
```

The command handles the full import flow automatically:

1. Computes MD5 hash and sends `init` request to get a presigned upload URL
2. Uploads the SQL file to R2
3. Triggers ingestion
4. Polls until import is complete

```
# Specify connection
php artisan d1:import database/seeds/data.sql --connection=d1
```

> **Note:** Like `d1:schema-dump`, the import command always uses the REST API. Worker-only users must also set `CF_D1_API_TOKEN`, `CF_D1_ACCOUNT_ID`, and `CF_D1_DATABASE_ID` in their `.env`.

### Time Travel

[](#time-travel)

D1 automatically creates restore points (bookmarks) for up to 30 days. Use `d1:time-travel` to get the current bookmark or restore your database to any point in time:

```
# Get the current bookmark
php artisan d1:time-travel

# Get the bookmark at a specific timestamp
php artisan d1:time-travel --timestamp="2024-01-15T10:30:00+00:00"

# Unix timestamps also work
php artisan d1:time-travel --timestamp=1705312200
```

To restore the database to a previous state:

```
# Restore to a specific bookmark
php artisan d1:time-travel --restore --bookmark="00000085-0000024c-00004c6d-abc123"

# Restore to a timestamp
php artisan d1:time-travel --restore --timestamp="2024-01-15T10:30:00+00:00"
```

> **Warning:** Restore is a destructive operation — it overwrites the database in place. In-flight queries will be cancelled. The command will prompt for confirmation before proceeding. The previous bookmark is shown after restore so you can undo if needed.

### Schema Dump

[](#schema-dump)

Export your D1 database schema (and optionally data) as a SQL file:

```
php artisan d1:schema-dump
```

This uses the [D1 export REST API](https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/export/) with polling mode. The dump is saved to `database/schema/{connection}-schema.sql`.

#### Options

[](#options)

```
# Schema only (no data)
php artisan d1:schema-dump --no-data

# Custom output path
php artisan d1:schema-dump --path=./backup.sql

# Delete migration files after dumping (same as native schema:dump --prune)
php artisan d1:schema-dump --prune

# Specify connection name
php artisan d1:schema-dump --connection=d1
```

> **Note:** `d1:schema-dump` always uses the REST API for export, even when the Worker driver is your primary connection. Worker-only users must also set `CF_D1_API_TOKEN`, `CF_D1_ACCOUNT_ID`, and `CF_D1_DATABASE_ID` in their `.env` for the dump command to work.

### Circuit Breaker

[](#circuit-breaker)

Prevents cascading failures when Cloudflare Workers experience cold starts or sustained outages. Instead of blocking for 30s+ on retries, the circuit breaker fails fast after consecutive failures.

**States:**

```
CLOSED → requests pass through normally
  ↓ (threshold consecutive failures)
OPEN → requests rejected immediately, no HTTP call
  ↓ (after cooldown seconds)
HALF_OPEN → one probe request allowed through
  ↓ success → CLOSED  |  failure → OPEN

```

**Enable in your config** (`config/database.php` or `config/d1-database.php`):

```
'd1' => [
    // ... other options ...
    'circuit_breaker' => [
        'enabled'      => env('CF_D1_CB_ENABLED', false),
        'threshold'    => env('CF_D1_CB_THRESHOLD', 5),     // failures before opening
        'cooldown'     => env('CF_D1_CB_COOLDOWN', 30),     // seconds before probe
        'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'),
    ],
],
```

**Handle the exception:**

```
use Ntanduy\CFD1\D1\Exceptions\CircuitBreakerOpenException;

try {
    $users = DB::connection('d1')->table('users')->get();
} catch (CircuitBreakerOpenException $e) {
    // Circuit is open — fail fast, use fallback or return cached data
}
```

> **Note:** Use `file` or `redis` as the `cache_driver`. Avoid `database` to prevent a dependency loop when D1 itself is down.

### Retry &amp; Backoff

[](#retry--backoff)

The driver automatically retries failed requests with exponential backoff and jitter:

- **Retried:** 5xx server errors, 429 rate limiting, connection timeouts
- **Not retried:** 4xx client errors (400, 401, 403, 404, etc.)

```
CF_D1_RETRIES=2          # Max retry attempts (default: 2)
CF_D1_RETRY_DELAY=100    # Base delay in ms (default: 100)
CF_D1_TIMEOUT=10         # Request timeout in seconds (default: 10)
CF_D1_CONNECT_TIMEOUT=5  # Connection timeout in seconds (default: 5)
```

Backoff formula: `delay × 2^(attempt-1) + random jitter (0-100ms)`

AttemptBase Delay (100ms)1~100-200ms2~200-300ms⚙️ Configuration Reference
--------------------------

[](#️-configuration-reference)

### Manual Setup (Alternative)

[](#manual-setup-alternative)

Instead of publishing the config, you can add the connection directly to `config/database.php`:

```
'connections' => [
    'd1' => [
        'driver' => 'd1',
        'd1_driver' => env('CF_D1_DRIVER', 'rest'),         // 'rest' or 'worker'
        'prefix' => '',
        'database' => env('CF_D1_DATABASE_ID', ''),

        // REST driver credentials
        'api' => 'https://api.cloudflare.com/client/v4',
        'auth' => [
            'token' => env('CF_D1_API_TOKEN', ''),
            'account_id' => env('CF_D1_ACCOUNT_ID', ''),
        ],

        // Worker driver credentials
        'worker_url' => env('CF_D1_WORKER_URL', ''),
        'worker_secret' => env('CF_D1_WORKER_SECRET', ''),
        'hmac' => env('CF_D1_HMAC', false),

        // Performance tuning
        'timeout' => env('CF_D1_TIMEOUT', 10),
        'connect_timeout' => env('CF_D1_CONNECT_TIMEOUT', 5),
        'retries' => env('CF_D1_RETRIES', 2),
        'retry_delay' => env('CF_D1_RETRY_DELAY', 100),

        // Sessions / Read Replication (Worker driver only)
        'session' => [
            'enabled' => env('CF_D1_SESSION_ENABLED', false),
            'mode'    => env('CF_D1_SESSION_MODE', 'first-unconstrained'),
        ],

        // Circuit breaker (optional)
        'circuit_breaker' => [
            'enabled'      => env('CF_D1_CB_ENABLED', false),
            'threshold'    => env('CF_D1_CB_THRESHOLD', 5),
            'cooldown'     => env('CF_D1_CB_COOLDOWN', 30),
            'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'),
        ],
    ],
],
```

### Options Reference

[](#options-reference)

OptionDefaultDescription`d1_driver``rest`Connection driver: `rest` (Cloudflare REST API) or `worker` (custom Worker)`database`—Your Cloudflare D1 Database ID`api``https://api.cloudflare.com/client/v4`Cloudflare API base URL (REST driver only)`auth.token`—Cloudflare API Token (REST driver only)`auth.account_id`—Cloudflare Account ID (REST driver only)`worker_url`—Your Worker URL (Worker driver only)`worker_secret`—Shared secret for Worker auth (Worker driver only)`hmac``false`Enable HMAC request signing for body-tamper protection and replay detection (Worker driver only)`timeout``10`HTTP request timeout in seconds`connect_timeout``5`HTTP connection timeout in seconds`retries``2`Max retry attempts on 5xx/429 errors`retry_delay``100`Base delay between retries in milliseconds`transaction_mode``silent`How transaction APIs are handled: `silent` (no-op), `log` (warn once), `exception` (throw)`session.enabled``false`Enable D1 sessions for read replication (Worker driver only)`session.mode``first-unconstrained`Session mode: `first-primary` or `first-unconstrained``read_write_splitting.enabled``false`Route SELECT to read replicas, writes to primary (Worker driver only)`read_write_splitting.sticky``true`After a write, route subsequent reads to write connector for consistency`read_write_splitting.read_mode``first-unconstrained`Session mode for the read connector`read_write_splitting.write_mode``first-primary`Session mode for the write connector`circuit_breaker.enabled``false`Enable circuit breaker for fail-fast behavior`circuit_breaker.threshold``5`Consecutive failures before opening the circuit`circuit_breaker.cooldown``30`Seconds before allowing a probe request`circuit_breaker.cache_driver``file`Laravel cache driver for circuit state (`file`, `redis`)### Environment Variables

[](#environment-variables)

```
# Driver selection
CF_D1_DRIVER=rest                    # 'rest' or 'worker'

# REST driver
CF_D1_API_TOKEN=your_api_token
CF_D1_ACCOUNT_ID=your_account_id
CF_D1_DATABASE_ID=your_database_id

# Worker driver
CF_D1_WORKER_URL=https://your-worker.workers.dev
CF_D1_WORKER_SECRET=your_shared_secret
CF_D1_HMAC=false                     # Enable HMAC request signing

# Performance tuning (optional)
CF_D1_TIMEOUT=10
CF_D1_CONNECT_TIMEOUT=5
CF_D1_RETRIES=2
CF_D1_RETRY_DELAY=100

# Transaction behavior (optional)
CF_D1_TRANSACTION_MODE=silent          # 'silent', 'log', or 'exception'

# Sessions / Read Replication (Worker driver only)
CF_D1_SESSION_ENABLED=false
CF_D1_SESSION_MODE=first-unconstrained

# Circuit breaker (optional)
CF_D1_CB_ENABLED=false
CF_D1_CB_THRESHOLD=5
CF_D1_CB_COOLDOWN=30
CF_D1_CB_CACHE_DRIVER=file
```

⚠️ Limitations
--------------

[](#️-limitations)

- **No real transactions** — D1 is stateless over HTTP and doesn't support `BEGIN`/`COMMIT`/`ROLLBACK`. The driver makes these methods no-ops so Laravel internals (auth, sessions, middleware) work without crashing.

    - `DB::transaction(Closure)` **will execute the closure**, but provides **no atomicity** — each query runs immediately and cannot be rolled back on failure.
    - `DB::transaction(Closure, attempts: N)` retries the closure on any exception, but without real deadlock detection or isolation.
    - Manual `beginTransaction()`, `commit()`, and `rollBack()` calls are also no-ops. `transaction_mode=log` warns once per connection/request; `transaction_mode=exception` throws immediately.
    - For atomic multi-statement execution, use **`batch()`** which leverages D1's native batch API:

        ```
        $connection->batch([
            ['sql' => 'INSERT INTO orders ...', 'params' => [...]],
            ['sql' => 'UPDATE inventory ...', 'params' => [...]],
        ]);
        ```
- **REST API latency** — Each query is an HTTP request routed through the Cloudflare API. The Worker driver offers significantly lower latency because the Worker is co-located with your D1 database. Latency varies by region, database size, and query complexity.
- **Sessions / Read Replication — Worker driver only** — The D1 Sessions API is only available via the Worker Binding. The REST API does not support sessions; all queries go to the primary database. This is a [Cloudflare platform limitation](https://developers.cloudflare.com/d1/best-practices/read-replication/).
- **Schema dump requires REST credentials** — `d1:schema-dump` uses the D1 export REST API. Even Worker-only users must set `CF_D1_API_TOKEN`, `CF_D1_ACCOUNT_ID`, and `CF_D1_DATABASE_ID`.
- **Export blocks queries** — During export, D1 may be unavailable for queries (Cloudflare limitation for large databases).
- **Bulk insert batch limit** — D1 batch supports max 100 statements. `bulkInsert()` automatically chunks larger datasets but each chunk is a separate HTTP call.
- **No streaming** — Large result sets are loaded entirely into memory.

🌱 Testing
---------

[](#-testing)

### PHP Tests

[](#php-tests)

```
vendor/bin/pest
```

### Worker Tests (Vitest)

[](#worker-tests-vitest)

```
cd Worker
npm ci
npm test
```

### Local Development with Worker

[](#local-development-with-worker)

Start the built-in Worker to test against a local D1 instance:

```
cd Worker
npm ci
npm run dev
```

🤝 Contributing
--------------

[](#-contributing)

Contributions are welcome! Please open an issue or pull request on [GitHub](https://github.com/TanDuy03/cloudflare-d1-database).

🔒 Security
----------

[](#-security)

If you discover any security related issues, please email  instead of using the issue tracker.

🎉 Credits
---------

[](#-credits)

- [TanDuy03](https://github.com/TanDuy03)
- [All Contributors](../../contributors)

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance94

Actively maintained with recent releases

Popularity36

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 99.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~22 days

Recently: every ~0 days

Total

29

Last Release

47d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/780a6cb42e3276222c7da4602f6580dd910922d133f923a7a8f8e4d42a4b6c9c?d=identicon)[TanDuy03](/maintainers/TanDuy03)

---

Top Contributors

[![TanDuy03](https://avatars.githubusercontent.com/u/95908945?v=4)](https://github.com/TanDuy03 "TanDuy03 (335 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

cloudflare-d1cloudflare-workersd1d1-databasedatabasedatabase-drivereloquentlaravellaravel-packagephpserverlesssqliteworkersphplaraveldatabasesqlitepdoeloquentlaravel-packagedrivercloudflared1database drivercloudflare-d1

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ntanduy-cloudflare-d1-database/health.svg)

```
[![Health](https://phpackages.com/badges/ntanduy-cloudflare-d1-database/health.svg)](https://phpackages.com/packages/ntanduy-cloudflare-d1-database)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M345](/packages/psalm-plugin-laravel)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.4M96](/packages/mongodb-laravel-mongodb)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M131](/packages/laravel-pulse)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.5k55.4M8.4k](/packages/larastan-larastan)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)[api-platform/laravel

API Platform support for Laravel

58171.4k14](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
