What Database Backends Does OpenSEO Support? D1 and PostgreSQL Options Explained
OpenSEO supports two database backends: Cloudflare D1 (SQLite) as the default and PostgreSQL as an optional scalable alternative, selected via the DATABASE_PROVIDER environment variable.
The open-source SEO toolkit from every-app/open-seo gives developers flexibility in choosing their data layer. You can start with D1 for simplicity and low operational overhead, then migrate to PostgreSQL when your project demands higher storage limits or advanced query capabilities.
How the Database Provider Is Configured
OpenSEO determines which backend to use through a centralized provider system defined in src/db/provider.ts. The implementation uses a discriminated union type that strictly limits valid options:
type DatabaseProvider = "d1" | "postgres";
export function getDatabaseProvider(): DatabaseProvider {
const provider = process.env.DATABASE_PROVIDER ?? "d1";
if (provider === "d1" || provider === "postgres") return provider;
throw new Error(`Unsupported DATABASE_PROVIDER "${String(provider)}". Expected "d1" or "postgres".`);
}
The type constraint appears in src/env.d.ts to ensure type safety across the codebase:
interface ProcessEnv {
readonly DATABASE_PROVIDER?: "d1" | "postgres";
}
Backend 1: Cloudflare D1 (SQLite)
D1 is the default database engine, requiring zero additional configuration. It leverages Cloudflare's serverless SQLite platform, making it ideal for:
- Small-to-medium projects
- Rapid prototyping and local development
- Deployments where managed database infrastructure is undesirable
When DATABASE_PROVIDER is unset or explicitly set to "d1", OpenSEO initializes a Drizzle-ORM D1 client through src/db/index.ts:
export const db = (getDatabaseProvider() === "postgres")
? drizzlePostgres(/* … */)
: drizzleD1(/* … */); // D1 path taken when provider is "d1"
Running Locally with D1
# D1 is the default — no environment variables required
pnpm dev
Backend 2: PostgreSQL
PostgreSQL becomes available when you set DATABASE_PROVIDER=postgres. This backend integrates through drizzle-orm/postgres-js and supports:
- Production workloads exceeding D1's storage constraints
- Complex analytical queries
- Existing PostgreSQL infrastructure
The configuration files drizzle-pg.config.ts and drizzle-prod.config.ts declare the dialect as "postgresql", ensuring schema migrations and query generation align with PostgreSQL semantics.
Required Environment Variables
| Variable | Purpose |
|---|---|
DATABASE_PROVIDER=postgres |
Activates PostgreSQL mode |
POSTGRES_DATABASE_URL |
Connection string for database access |
In production Cloudflare deployments, this typically references a Hyperdrive Postgres binding for connection pooling and latency optimization.
Running Locally with PostgreSQL
export DATABASE_PROVIDER=postgres
export POSTGRES_DATABASE_URL=postgres://user:pass@localhost:5432/open_seo
pnpm dev
Production Deployment Configuration
DATABASE_PROVIDER=postgres
POSTGRES_DATABASE_URL=postgres://user:pass@host:5432/db
The deployment pipeline in alchemy.run.ts automatically handles Hyperdrive binding when these variables are present.
Database Client Initialization
The unified database export in src/db/index.ts provides a single db interface regardless of backend. This abstraction lets application code remain provider-agnostic:
export const db = (getDatabaseProvider() === "postgres")
? drizzlePostgres(/* postgres client config */)
: drizzleD1(/* D1 binding config */);
Both clients expose identical Drizzle-ORM interfaces, so queries, transactions, and migrations work transparently across backends.
Migrating Between Backends
OpenSEO includes a dedicated migration utility for moving from D1 to PostgreSQL without data loss. The script scripts/migrate-d1-to-postgres.ts handles:
- Schema translation between SQLite and PostgreSQL dialects
- Data export from D1 and import into PostgreSQL
- Type conversion and constraint preservation
Run this when your D1 instance approaches storage limits or when PostgreSQL-specific features become necessary.
Key Files in the OpenSEO Database Layer
| File | Responsibility |
|---|---|
src/db/provider.ts |
Provider detection and validation |
src/env.d.ts |
Environment variable type definitions |
src/db/index.ts |
Runtime client selection and export |
drizzle-pg.config.ts |
PostgreSQL-specific ORM configuration |
drizzle-prod.config.ts |
Production PostgreSQL settings |
scripts/migrate-d1-to-postgres.ts |
D1-to-PostgreSQL data migration |
Summary
- Two backends supported: Cloudflare D1 (SQLite) and PostgreSQL, configured through
DATABASE_PROVIDER - D1 is default: Zero-config local and serverless deployment via
drizzle-ormD1 adapter - PostgreSQL for scale: Activated with
DATABASE_PROVIDER=postgresandPOSTGRES_DATABASE_URL - Type-safe provider system:
src/db/provider.tsenforces valid values at build and runtime - Seamless abstraction:
src/db/index.tsexposes a unifieddbclient regardless of backend - Migration path included:
scripts/migrate-d1-to-postgres.tsenables backend transitions
Frequently Asked Questions
How do I switch from D1 to PostgreSQL in OpenSEO?
Set DATABASE_PROVIDER=postgres and provide POSTGRES_DATABASE_URL in your environment. For existing D1 data, run scripts/migrate-d1-to-postgres.ts to transfer records before switching the provider in production.
What happens if I set an invalid DATABASE_PROVIDER value?
The getDatabaseProvider() function in src/db/provider.ts throws a runtime error: Unsupported DATABASE_PROVIDER "invalid". Expected "d1" or "postgres". This prevents accidental misconfiguration from reaching production.
Does OpenSEO support MySQL or other database engines?
No. According to the source code in src/db/provider.ts, the DatabaseProvider type is strictly limited to "d1" | "postgres". Adding support for additional backends would require extending this union type and implementing corresponding Drizzle-ORM client initialization in src/db/index.ts.
Can I use PostgreSQL locally without Cloudflare Hyperdrive?
Yes. For local development, POSTGRES_DATABASE_URL can point to any accessible PostgreSQL instance—local Docker containers, managed services, or remote servers. Hyperdrive binding is only required for optimized Cloudflare Workers production deployments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →