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) |
Related
- SDK Conventions - Pagination patterns and common usage
- API Coverage - Full list of supported endpoints
- Workers KV - Key-value storage alternative