# Which ORM Does OpenSEO Use for Database Migrations?

> Discover which ORM OpenSEO uses for database migrations. Learn about type-safe schema definitions and dual database support for Cloudflare D1 and PostgreSQL with Drizzle ORM.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-09-06

---

**OpenSEO uses Drizzle ORM for database migrations, with type-safe schema definitions and dual database support for both Cloudflare D1 (SQLite) and PostgreSQL.**

The open-source OpenSEO repository (`every-app/open-seo`) implements a modern, migration-ready data layer built entirely on **Drizzle ORM** (`drizzle-orm`). According to the source code, the project maintains separate Drizzle configurations for SQLite-based D1 and PostgreSQL instances, using Drizzle's built-in migration tooling to apply versioned schema changes across both environments.

## Why Drizzle ORM Powers OpenSEO Database Migrations

Drizzle ORM provides OpenSEO with a **type-safe, SQL-like DSL** for schema definition without sacrificing performance or developer experience. The repository leverages Drizzle's modular architecture, importing database-specific cores from `drizzle-orm/sqlite-core` and `drizzle-orm/pg-core` depending on the target environment.

This design choice enables OpenSEO to:
- Deploy to **Cloudflare D1** (SQLite) for edge-compatible deployments
- Run **PostgreSQL** for traditional server-based infrastructure
- Share schema logic while maintaining environment-specific optimizations

## How OpenSEO Configures Drizzle ORM for Dual Databases

### SQLite/D1 Schema Definition

In [`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts), OpenSEO defines tables using Drizzle's SQLite core:

```typescript
// src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const projects = sqliteTable("projects", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
});

```

### PostgreSQL Schema Definition

The same table structure is mirrored for PostgreSQL in [`src/db/pg/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/schema.ts):

```typescript
// src/db/pg/schema.ts
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const projects = pgTable("projects", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  createdAt: timestamp("created_at", { mode: "date" }).defaultNow(),
});

```

Both definitions maintain **identical column semantics** while using Drizzle's database-specific type primitives.

## Running Database Migrations in OpenSEO

### Creating Drizzle Clients

OpenSEO instantiates separate Drizzle clients for each database backend. The D1 client in [`src/db/d1/client.ts`](https://github.com/every-app/open-seo/blob/main/src/db/d1/client.ts):

```typescript
// src/db/d1/client.ts
import { drizzle } from "drizzle-orm/d1";
import { env } from "../env";

export const d1Db = drizzle(env.DB, { schema });

```

The PostgreSQL client in [`src/db/pg/client.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/client.ts):

```typescript
// src/db/pg/client.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { schema } from "./schema";

export const pgDb = drizzle(postgres(env.PG_CONNECTION_STRING), { schema });

```

### Migration Script Implementation

The repository includes a custom migration script at [`scripts/migrate-d1-to-postgres.ts`](https://github.com/every-app/open-seo/blob/main/scripts/migrate-d1-to-postgres.ts) that applies Drizzle migrations across both databases:

```typescript
// scripts/migrate-d1-to-postgres.ts
import { drizzle } from "drizzle-orm/d1";
import { drizzle as pgDrizzle } from "drizzle-orm/postgres-js";
import { schema as d1Schema } from "../src/db/schema";
import { schema as pgSchema } from "../src/db/pg/schema";

const d1 = drizzle(env.DB, { schema: d1Schema });
const pg = pgDrizzle(env.PG_CONNECTION_STRING, { schema: pgSchema });

await d1.migrate();   // applies any pending D1 migrations
await pg.migrate();   // applies matching PostgreSQL migrations

```

The `drizzle.migrate()` method invokes **Drizzle's migration CLI** (`drizzle-migrate`) under the hood, reading versioned SQL files and applying pending changes transactionally.

## Key Configuration Files for OpenSEO ORM Setup

| Purpose | File Path |
|---------|-----------|
| Global Drizzle configuration | [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) |
| SQLite/D1 schema definitions | [`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts) |
| PostgreSQL schema definitions | [`src/db/pg/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/schema.ts) |
| D1 client instantiation | [`src/db/d1/client.ts`](https://github.com/every-app/open-seo/blob/main/src/db/d1/client.ts) |
| PostgreSQL client instantiation | [`src/db/pg/client.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/client.ts) |
| Cross-database migration script | [`scripts/migrate-d1-to-postgres.ts`](https://github.com/every-app/open-seo/blob/main/scripts/migrate-d1-to-postgres.ts) |

The [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) file at the repository root centralizes migration settings, output paths, and database connection parameters for the Drizzle CLI.

## Summary

- **OpenSEO uses Drizzle ORM** as its sole database abstraction layer for both schema definition and migrations
- **Dual-database architecture** supports Cloudflare D1 (SQLite) and PostgreSQL through separate Drizzle core packages
- **Type-safe migrations** are applied via `drizzle.migrate()` or the `drizzle-migrate` CLI
- **Shared schema patterns** across database engines minimize code duplication while preserving engine-specific optimizations

## Frequently Asked Questions

### Does OpenSEO support other ORMs besides Drizzle?

No. As implemented in `every-app/open-seo`, the entire data layer is built on Drizzle ORM exclusively. No Prisma, TypeORM, or other ORM dependencies exist in the codebase.

### How does OpenSEO handle schema changes across D1 and PostgreSQL?

The repository maintains parallel schema files ([`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts) for D1, [`src/db/pg/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/schema.ts) for PostgreSQL). The migration script [`scripts/migrate-d1-to-postgres.ts`](https://github.com/every-app/open-seo/blob/main/scripts/migrate-d1-to-postgres.ts) applies Drizzle migrations to both backends, ensuring schema consistency.

### What Drizzle packages does OpenSEO install?

OpenSEO imports `drizzle-orm` with database-specific subpackages: `drizzle-orm/sqlite-core`, `drizzle-orm/pg-core`, `drizzle-orm/d1`, and `drizzle-orm/postgres-js`. These provide type-safe clients for each database engine.

### Can I use Drizzle migrations with Cloudflare D1 in my own project?

Yes. The OpenSEO source demonstrates production-ready Drizzle ORM usage with D1. The key requirements are: install `drizzle-orm/d1`, configure a Drizzle client with your D1 binding, and run `drizzle.migrate()` to apply pending migrations.