Skip to main content

Cloudflare D1

ormed_d1 is the Cloudflare D1 adapter for Ormed. It compiles SQLite-compatible SQL through ormed_sqlite_core and executes statements over the D1 HTTP API.

Install

dependencies:
ormed: ^0.4.0
ormed_d1: ^0.3.0

Choose a connection mode

Use the mode that matches where the D1 request runs:

RuntimeAPICredential boundary
Server-side Dart with Cloudflare API accessD1Database.connect(...)The Dart process holds the Cloudflare credentials
Browser or Worker client calling your applicationD1Database.fromEndpoint(...)Your application endpoint authenticates the caller; no D1 token ships to the client
Dart running inside a Cloudflare Worker with a native bindingD1Database.fromBinding(...)The Worker runtime supplies the binding directly

Direct database access

Code generation is optional for D1. The direct facade is useful for raw SQL, ad-hoc tables, migrations, and small services:

import 'package:ormed_d1/ormed_d1.dart';

Future<void> main() async {
final db = await D1Database.connect(
accountId: 'your-account-id',
databaseId: 'your-database-id',
apiToken: 'your-api-token',
);

final rows = await db.queryRaw('SELECT 1 AS ok');
print(rows.first['ok']);
await db.close();
}

Do not put the Cloudflare API token in a browser bundle. Use an application-owned endpoint instead:

final db = await D1Database.fromEndpoint(
endpoint: Uri.parse('https://example.com/api/database/query'),
batchEndpoint: Uri.parse('https://example.com/api/database/batch'),
);

The endpoint receives {sql, params}. It may return the D1 binding result shape or the Cloudflare API result shape. When present, batchEndpoint accepts {statements: [{sql, params}, ...]} for atomic batches.

Inside a Cloudflare Worker, use the host-neutral binding contract without an HTTP hop:

final db = await D1Database.fromBinding(binding: env.d1('DB'));

The binding types are exported from package:ormed_d1/d1_binding.dart so a platform bridge can implement D1DatabaseBinding without importing a Cloudflare-specific runtime into the shared package.

Use in code (DataSource)

The recommended path is generated config.dart + datasource.dart.

import 'package:your_app/src/database/datasource.dart';

Future<void> main() async {
final ds = createDataSource(connection: 'd1');
await ds.init();
await ds.dispose();
}

Code-first Helper APIs

Use model-registry extensions when you want explicit setup in code:

  • registry.d1DataSourceOptions(...)
  • registry.d1DataSource(...)
  • registry.d1DataSourceOptionsFromEnv(...)
  • registry.d1DataSourceFromEnv(...)

Configure (ormed.yaml)

driver:
type: d1
options:
accountId: ${DB_D1_ACCOUNT_ID}
databaseId: ${DB_D1_DATABASE_ID}
apiToken: ${DB_D1_API_TOKEN}
baseUrl: ${DB_D1_BASE_URL:-https://api.cloudflare.com/client/v4}

Environment Variables

d1DataSourceOptionsFromEnv(...) / d1DataSourceFromEnv(...) recognize:

  • D1_ACCOUNT_ID, CF_ACCOUNT_ID, DB_D1_ACCOUNT_ID
  • D1_DATABASE_ID, DB_D1_DATABASE_ID
  • D1_API_TOKEN, D1_SECRET, DB_D1_API_TOKEN
  • D1_BASE_URL, DB_D1_BASE_URL
  • D1_RETRY_ATTEMPTS, DB_D1_RETRY_ATTEMPTS
  • D1_REQUEST_TIMEOUT_MS, DB_D1_REQUEST_TIMEOUT_MS
  • D1_RETRY_BASE_DELAY_MS, DB_D1_RETRY_BASE_DELAY_MS
  • D1_RETRY_MAX_DELAY_MS, DB_D1_RETRY_MAX_DELAY_MS
  • D1_DEBUG_LOG, DB_D1_DEBUG_LOG

Minimum required credentials:

  • account id
  • database id
  • API token

Quick Verification

Use a simple query like SELECT 1 AS ok through your app bootstrap path. Avoid probing blocked functions (for example sqlite_version()).

Options

OptionTypeDefaultDescription
accountIdStringCloudflare account id.
databaseIdStringD1 database id.
apiTokenStringCloudflare API token with D1 access.
baseUrlStringhttps://api.cloudflare.com/client/v4Cloudflare API base URL.
maxAttemptsint5Max request attempts (initial + retries).
requestTimeoutMsint30000Per-request timeout in milliseconds.
retryBaseDelayMsint250Initial retry backoff delay.
retryMaxDelayMsint3000Max retry backoff delay cap.
debugLogboolfalseEnables transport request/response logging.

Notes

  • D1 is remote and HTTP-backed, so expect higher latency than local SQLite.
  • Retry/backoff is built into the transport and configurable through options/env.

Troubleshooting

Slow tests or "hanging" runs

Most long waits are retry/timeout behavior rather than deadlocks. Reduce retry envelope while debugging:

  • D1_RETRY_ATTEMPTS=1
  • D1_REQUEST_TIMEOUT_MS=5000
  • D1_DEBUG_LOG=1

This makes failures surface quickly and prints request/response timing.

not authorized to use function: sqlite_version

Cloudflare D1 blocks some SQLite functions. Avoid runtime probes like:

  • SELECT sqlite_version()

Use neutral checks such as:

  • SELECT 1 AS ok

401/403 auth errors

Verify all required values are present and mapped correctly:

  • account id (D1_ACCOUNT_ID / CF_ACCOUNT_ID / DB_D1_ACCOUNT_ID)
  • database id (D1_DATABASE_ID / DB_D1_DATABASE_ID)
  • API token (D1_API_TOKEN / D1_SECRET / DB_D1_API_TOKEN)

If requests still fail, lower retries/timeouts temporarily to surface root errors quickly while debugging.

Read This Next