Table of Contents

D1 Serverless Database

D1 is Cloudflare's native serverless SQL database built on SQLite. This API allows you to manage D1 databases, execute SQL queries, and perform import/export operations programmatically.

Overview

public class D1Service(ICloudflareApiClient cf)
{
    public async Task<IReadOnlyList<D1QueryResult>> ExecuteQueryAsync(
        string databaseId,
        string sql)
    {
        return await cf.Accounts.D1.QueryAsync(databaseId, sql);
    }
}

API Limits

Limit Value (Workers Paid)
Max databases 50,000 per account
Max database size 10 GB
Max account storage 1 TB
Max SQL statement length 100 KB
Max query duration 30 seconds
Max bound parameters 100 per query
Max row/string/BLOB size 2 MB
Max columns per table 100
Warning

D1's REST API is subject to global Cloudflare API rate limits and is best suited for administrative use. For high-throughput query access outside Workers, consider creating a proxy Worker with the D1 Worker binding.

Database Operations

Creating a Database

// Basic creation
var db = await cf.Accounts.D1.CreateAsync("my-database");

Console.WriteLine($"Database ID: {db.Uuid}");
Console.WriteLine($"Name: {db.Name}");
Console.WriteLine($"Version: {db.Version}");

With Location Hint

Suggest a geographic region for database placement:

// Create database in Western Europe
var db = await cf.Accounts.D1.CreateAsync(
    "eu-database",
    primaryLocationHint: R2LocationHint.WestEurope
);

Console.WriteLine($"Running in region: {db.RunningInRegion}");

With Jurisdiction (Data Residency)

Enforce data residency within a specific boundary:

// Create EU-jurisdictional database for GDPR compliance
var db = await cf.Accounts.D1.CreateAsync(
    "gdpr-database",
    jurisdiction: D1Jurisdiction.EuropeanUnion
);
Note

When specifying a jurisdiction, the primaryLocationHint parameter is ignored. The jurisdiction takes precedence and guarantees data residency within the specified region.

Getting a Database

var db = await cf.Accounts.D1.GetAsync(databaseId);

Console.WriteLine($"Name: {db.Name}");
Console.WriteLine($"Size: {db.FileSize} bytes");
Console.WriteLine($"Tables: {db.NumTables}");
Console.WriteLine($"Region: {db.RunningInRegion}");

Listing Databases

// List all databases automatically
await foreach (var db in cf.Accounts.D1.ListAllAsync())
{
    Console.WriteLine($"{db.Name}: {db.Uuid}");
}

With Filters

// Filter by name
var filters = new ListD1DatabasesFilters(Name: "production");

await foreach (var db in cf.Accounts.D1.ListAllAsync(filters))
{
    Console.WriteLine(db.Name);
}

Manual Pagination

var page = await cf.Accounts.D1.ListAsync(new ListD1DatabasesFilters(
    Page: 1,
    PerPage: 50
));

Console.WriteLine($"Page {page.PageInfo.Page}, returned {page.Items.Count} items");

foreach (var db in page.Items)
{
    Console.WriteLine(db.Name);
}

// Fetch next page if current page is full
if (page.Items.Count >= 50)
{
    var nextPage = await cf.Accounts.D1.ListAsync(new ListD1DatabasesFilters(
        Page: 2,
        PerPage: 50
    ));
}
Warning

The D1 API returns total_count=0 and total_pages=0 in the pagination info, which is inconsistent with other Cloudflare APIs. Use ListAllAsync for automatic pagination, or check if the current page is full to determine if more pages exist.

Updating a Database

Update database configuration such as read replication:

// Enable read replication
var updated = await cf.Accounts.D1.UpdateAsync(
    databaseId,
    new UpdateD1DatabaseOptions(
        ReadReplication: new D1ReadReplication("auto")
    )
);

Console.WriteLine($"Replication mode: {updated.ReadReplication?.Mode}");

Deleting a Database

await cf.Accounts.D1.DeleteAsync(databaseId);
Warning

Deleting a database permanently removes all data. This action cannot be undone.

Query Operations

Simple Queries

var results = await cf.Accounts.D1.QueryAsync(databaseId, "SELECT * FROM users");

foreach (var result in results)
{
    if (result.Success)
    {
        Console.WriteLine($"Duration: {result.Meta.Duration}ms");
        Console.WriteLine($"Rows read: {result.Meta.RowsRead}");

        foreach (var row in result.Results)
        {
            Console.WriteLine(row);
        }
    }
}

Parameterized Queries

Use ? placeholders to prevent SQL injection:

// Safe parameterized query
var results = await cf.Accounts.D1.QueryAsync(
    databaseId,
    "SELECT * FROM users WHERE id = ? AND status = ?",
    new object?[] { userId, "active" }
);

Insert Operations

var results = await cf.Accounts.D1.QueryAsync(
    databaseId,
    "INSERT INTO users (name, email) VALUES (?, ?)",
    new object?[] { "John Doe", "[email protected]" }
);

var meta = results[0].Meta;
Console.WriteLine($"Rows inserted: {meta.Changes}");
Console.WriteLine($"Last row ID: {meta.LastRowId}");

Multiple Statements

Execute multiple SQL statements in a single request:

var results = await cf.Accounts.D1.QueryAsync(
    databaseId,
    """
    INSERT INTO users (name) VALUES ('Alice');
    INSERT INTO users (name) VALUES ('Bob');
    SELECT COUNT(*) as total FROM users
    """
);

// Each statement produces a separate result
Console.WriteLine($"Statement count: {results.Count}");
Note

When executing multiple statements, parameter binding is not supported. Use literal values or execute statements separately with parameters.

Typed Query Results

Deserialize results directly to strongly-typed objects:

public record User(long Id, string Name, string Email);

var results = await cf.Accounts.D1.QueryAsync<User>(
    databaseId,
    "SELECT id, name, email FROM users WHERE active = ?",
    new object?[] { true }
);

foreach (var user in results[0].Results)
{
    Console.WriteLine($"{user.Name}: {user.Email}");
}

Raw Query Format

For better performance with large result sets, use the raw format which returns rows as arrays instead of objects:

var results = await cf.Accounts.D1.QueryRawAsync(
    databaseId,
    "SELECT id, name, email FROM users"
);

var result = results[0];
Console.WriteLine($"Columns: {string.Join(", ", result.Results.Columns)}");

foreach (var row in result.Results.Rows)
{
    // Row values are in column order
    Console.WriteLine($"ID: {row[0]}, Name: {row[1]}, Email: {row[2]}");
}

Export Operations

Export a database to SQL format for backup or migration:

// Start export
var export = await cf.Accounts.D1.StartExportAsync(databaseId);
var bookmark = export.AtBookmark;

// Poll until complete
while (export.Status != "complete")
{
    await Task.Delay(1000);
    export = await cf.Accounts.D1.PollExportAsync(databaseId, bookmark!);
}

// Download the SQL file
Console.WriteLine($"Download URL: {export.Result?.SignedUrl}");

Export Options

Control what data is included in the export:

// Schema only (no data)
var schemaExport = await cf.Accounts.D1.StartExportAsync(
    databaseId,
    new D1ExportDumpOptions(NoData: true)
);

// Data only (no schema)
var dataExport = await cf.Accounts.D1.StartExportAsync(
    databaseId,
    new D1ExportDumpOptions(NoSchema: true)
);

// Specific tables only
var tablesExport = await cf.Accounts.D1.StartExportAsync(
    databaseId,
    new D1ExportDumpOptions(Tables: new[] { "users", "orders" })
);
Note

The database is unavailable for queries during export operations. Exports that aren't polled will auto-cancel.

Import Operations

Import SQL data into a database:

// Step 1: Compute MD5 hash of your SQL file
var sqlContent = await File.ReadAllBytesAsync("backup.sql");
var md5 = Convert.ToHexString(MD5.HashData(sqlContent)).ToLower();

// Step 2: Request upload URL
var initResponse = await cf.Accounts.D1.StartImportAsync(databaseId, md5);
var uploadUrl = initResponse.UploadUrl;
var filename = initResponse.Filename;

// Step 3: Upload the SQL file to the signed URL
using var httpClient = new HttpClient();
using var content = new ByteArrayContent(sqlContent);
await httpClient.PutAsync(uploadUrl, content);

// Step 4: Start the import
var importResponse = await cf.Accounts.D1.CompleteImportAsync(databaseId, md5, filename!);
var bookmark = importResponse.AtBookmark;

// Step 5: Poll until complete
while (importResponse.Status != "complete")
{
    await Task.Delay(1000);
    importResponse = await cf.Accounts.D1.PollImportAsync(databaseId, bookmark!);
}

Console.WriteLine($"Imported {importResponse.Result?.NumQueries} queries");
Warning

The database is blocked during import operations. Plan imports during maintenance windows.

Models Reference

D1Database

Property Type Description
Uuid string Unique database identifier (UUID format)
Name string Database name
CreatedAt DateTimeOffset? When the database was created
FileSize long? Database size in bytes
NumTables int? Number of tables in the database
Version string? Database version: "production" or "alpha"
ReadReplication D1ReadReplication? Read replication configuration
RunningInRegion string? Region code where the database is running

D1QueryMeta

Property Type Description
ChangedDb bool Whether the database was modified
Changes int Number of rows modified
Duration double Query execution time in milliseconds
LastRowId long Row ID of last inserted row
RowsRead long Number of rows read (including indices)
RowsWritten long Number of rows written (including indices)
ServedByRegion string? Region that handled the query
ServedByPrimary bool? Whether handled by primary instance
SizeAfter long? Database size after the query

D1Jurisdiction (Extensible Enum)

Jurisdictions guarantee data residency within specific geographic or regulatory boundaries.

Constant Value Description
EuropeanUnion eu EU data residency (GDPR compliance)
FedRamp fedramp US federal compliance (Enterprise only)
// EU jurisdiction for GDPR compliance
var jurisdiction = D1Jurisdiction.EuropeanUnion;

// Using custom values for new jurisdictions
D1Jurisdiction customJurisdiction = "new-jurisdiction";

ListD1DatabasesFilters

Property Type Description
Name string? Filter by database name (partial match)
Page int? Page number (1-based)
PerPage int? Number of results per page

D1ExportDumpOptions

Property Type Description
NoData bool? Export only schema (no data)
NoSchema bool? Export only data (no schema)
Tables IReadOnlyList<string>? Filter to specific tables

Common Patterns

Schema Management

public class SchemaManager(ICloudflareApiClient cf)
{
    public async Task CreateTablesAsync(string databaseId)
    {
        await cf.Accounts.D1.QueryAsync(databaseId, """
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT UNIQUE NOT NULL,
                name TEXT NOT NULL,
                created_at TEXT DEFAULT (datetime('now'))
            );

            CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
        """);
    }
}

Connection Pooling Pattern

public class D1Repository(ICloudflareApiClient cf, string databaseId)
{
    public async Task<User?> GetUserByEmailAsync(string email)
    {
        var results = await cf.Accounts.D1.QueryAsync<User>(
            databaseId,
            "SELECT * FROM users WHERE email = ?",
            new object?[] { email }
        );

        return results[0].Results.FirstOrDefault();
    }

    public async Task<User> CreateUserAsync(string name, string email)
    {
        var results = await cf.Accounts.D1.QueryAsync<User>(
            databaseId,
            """
            INSERT INTO users (name, email) VALUES (?, ?)
            RETURNING *
            """,
            new object?[] { name, email }
        );

        return results[0].Results[0];
    }
}

Backup and Restore

public class BackupService(ICloudflareApiClient cf)
{
    public async Task<string> BackupDatabaseAsync(string databaseId)
    {
        // Start export
        var export = await cf.Accounts.D1.StartExportAsync(databaseId);

        // Poll until complete
        while (export.Status != "complete")
        {
            await Task.Delay(2000);
            export = await cf.Accounts.D1.PollExportAsync(databaseId, export.AtBookmark!);

            if (export.Status == "error")
                throw new Exception($"Export failed: {export.Error}");
        }

        return export.Result!.SignedUrl!;
    }
}

Required Permissions

Permission Scope Level
D1 Account Read (for listing and querying)
D1 Account Write (for create, update, delete, import/export)