Database Migrations and Drizzle Schema Structure in OpenSEO
OpenSEO utilizes Drizzle-Kit to manage a dual-provider database architecture supporting both Cloudflare D1 (SQLite) and PostgreSQL, with 20+ versioned SQL migrations for PostgreSQL housed in drizzle-pg/ and runtime schema generation for D1.
The every-app/open-seo repository implements a provider-agnostic, type-safe database layer using Drizzle ORM. The schema is designed to work seamlessly across SQLite (via Cloudflare D1) and PostgreSQL environments while maintaining a single source of truth for table definitions under src/db.
Drizzle Schema Architecture
OpenSEO employs a provider-aware barrel pattern to abstract database dialect differences. Two Drizzle-Kit configuration files manage the separate providers:
drizzle.config.ts– Targets Cloudflare D1 (SQLite) using thed1-httpdriverdrizzle-prod.config.ts– Targets PostgreSQL for production deployments
Each configuration points to a barrel file that re-exports schema modules from provider-specific subdirectories:
- SQLite barrel:
src/db/d1/schema.tsre-exports from../app.schema,../audit.schema,../sam.schema,../better-auth-schema,../billing.schema,../ga4.schema,../gsc.schema, and../telemetry.schema - PostgreSQL barrel:
src/db/pg/schema.tsperforms the same re-exports but resolves to thepg/directory variants
This structure allows application code to import tables without knowing the underlying driver, ensuring type safety across both database providers.
Core Schema Modules
The database is organized into nine primary modules under src/db/, each handling distinct domain concerns:
-
Authentication & Organizations (
src/db/better-auth-schema.ts): Containsorganization,user,account,member,invitation, andsessiontables with proper foreign-key constraints and indexes for the Better Auth integration. -
Projects & Keywords (
src/db/app.schema.ts): Managesprojects,saved_keywords,saved_keyword_tags,saved_keyword_tag_assignments, andkeyword_metricstables for SEO campaign data. -
Rank Tracking (
src/db/app.schema.ts): Includesrank_tracking_configs,rank_tracking_keywords,rank_check_runs, andrank_snapshotswith composite indexes for performance. -
Audits (
src/db/audit.schema.ts): Definesaudits,audit_pages, andaudit_lighthouse_resultsfor storing technical SEO audit data. -
Search Console & GA4 (
src/db/gsc.schema.tsandsrc/db/ga4.schema.ts): Housesgsc_connectionsandga4_connectionstables for third-party API integrations. -
Backlinks (
src/db/app.schema.ts): Containsbacklink_snapshotsfor external link monitoring. -
Telemetry (
src/db/telemetry.schema.ts): Stores internal metrics in thetelemetrytable. -
SAM (Search-Ads-Metrics) (
src/db/sam.schema.ts): Dedicated module for search advertising data tables. -
Billing (
src/db/billing.schema.ts): Handles subscription and payment-related tables.
Each table is declared using Drizzle-ORM helpers (sqliteTable, pgTable, text, integer, real, index, uniqueIndex) with dialect-specific column types, foreign-key constraints, partial unique indexes, and default values generated at runtime.
Database Migration System
OpenSEO maintains separate migration strategies for its two database providers. The PostgreSQL implementation uses versioned SQL files, while the D1 implementation relies on runtime schema synchronization.
PostgreSQL Migration Files
All PostgreSQL migrations reside in the drizzle-pg/ directory and follow sequential numbering (e.g., 0000_*, 0001_*). The current schema includes 20+ migration files that build the database incrementally:
0000_fixed_nico_minoru.sql– Creates foundational tables includingaudit_lighthouse_results,audit_pages,audits,keyword_metrics,projects,rank_check_runs, andrank_snapshots0001_striped_bulldozer.sql– Adds foreign-key constraints and performance indexes to core tables0002_clean_moira_mactaggert.sql– Introducesrank_tracking_configswith unique indexes0003_sturdy_may_parker.sql– Createsrank_tracking_keywordswith uniqueness rules0004_dashing_betty_ross.sql– Addsrank_check_runswith a partial-unique constraint enforcing one active run per configuration0005_talented_wild_pack.sql– Createsrank_snapshotswith composite indexes for fast lookups0006_location_name.sql– Adds thelocation_namecolumn torank_tracking_configs0007_same_marvel_zombies.sql– Introduces keyword tagging:saved_keywords,saved_keyword_tags, andsaved_keyword_tag_assignments0008_yummy_annihilus.sql– Adds indexes and constraints tokeyword_metrics0009_supreme_captain_stacy.sql– Createsuser_onboarding_answerstable0010_overrated_amazoness.sql– Addsaccount,session, and related authentication indexes0011_friendly_morlun.sql– Introducesinvitationandmembertables for organization management0012_dashboard.sql– Createsorganization_activation_stateandproject_activation_statetracking tables0013_sleepy_black_tarantula.sql– Addsbacklink_snapshots0014_solid_centennial.sql– Createsverificationtable0015_sticky_dagger.sql– Addsgsc_connectionsfor Google Search Console integration0016_panoramic_blob.sqland0017_ga4_connections.sql– Createga4_connectionsfor Google Analytics 40018_drop_reddit_attributions.sql– Removes the deprecatedreddit_attributionstable0019_clammy_selene.sql– Final cleanup migration adding missing indexes and constraints
D1 (SQLite) Runtime Schema
The Cloudflare D1 provider does not use static .sql migration files. Instead, the schema is generated directly from the TypeScript definitions at runtime. All structural changes for D1 are reflected immediately in the schema modules under src/db/d1/, and Drizzle-Kit applies these changes through the D1 HTTP API without intermediate SQL files.
Running Migrations
For PostgreSQL deployments, execute the migration suite using the Drizzle-Kit CLI:
# Install the CLI if not present
pnpm add -D drizzle-kit
# Execute all pending migrations in order
pnpm drizzle-kit migrate --config drizzle-prod.config.ts
The CLI reads drizzle-prod.config.ts, connects to the target database, and executes the numbered SQL files in drizzle-pg/ sequentially. To generate a new migration after schema changes:
pnpm drizzle-kit generate --config drizzle-prod.config.ts --name add_feature_table
How the Pieces Fit Together
The database architecture follows a four-layer workflow:
-
Schema Definition – TypeScript modules under
src/db/*export Drizzle-ORM table objects using provider-specific helpers (sqliteTablevspgTable). -
Provider Barrels –
src/db/d1/schema.tsandsrc/db/pg/schema.tsre-export these modules, allowing the application to import from a single path while the correct dialect resolves at runtime. -
Configuration –
drizzle.config.ts(development/D1) anddrizzle-prod.config.ts(production/PostgreSQL) point to the appropriate barrel and specify the dialect and driver. -
Migration Execution – For PostgreSQL, the CLI executes the
drizzle-pg/*.sqlfiles in order. For D1, the schema is pushed directly via the Drizzle-Kit API.
Importing Tables in Application Code
Use the barrel imports to query tables with full TypeScript type safety:
import { db } from "@every-app/sdk/drizzle";
import { projects } from "./src/db/app.schema";
import { eq, desc } from "drizzle-orm";
// Fetch active projects for an organization
const orgId = "org_123";
const activeProjects = await db
.select()
.from(projects)
.where(eq(projects.organizationId, orgId))
.orderBy(desc(projects.createdAt));
This import pattern works identically for both SQLite and PostgreSQL deployments because the barrel file abstracts the driver-specific implementation details.
Summary
- OpenSEO supports dual database providers: Cloudflare D1 (SQLite) for edge deployment and PostgreSQL for traditional hosting.
- The Drizzle schema is organized into nine modular files under
src/db/, covering authentication, audits, rank tracking, billing, and integrations. - PostgreSQL migrations are versioned SQL files (20+ total) stored in
drizzle-pg/, executed sequentially by Drizzle-Kit. - D1 uses runtime schema generation rather than static migration files, applying changes directly from TypeScript definitions.
- Provider barrels at
src/db/d1/schema.tsandsrc/db/pg/schema.tsenable dialect-agnostic imports throughout the application.
Frequently Asked Questions
What is the difference between D1 and PostgreSQL migrations in OpenSEO?
PostgreSQL migrations are explicit SQL files in the drizzle-pg/ directory that Drizzle-Kit executes sequentially, providing a versioned history of schema changes. D1 (SQLite) migrations do not use static SQL files; instead, the schema is generated and applied directly from the TypeScript definitions at runtime using the Drizzle-Kit push command.
How do I run database migrations in OpenSEO?
For PostgreSQL, run pnpm drizzle-kit migrate --config drizzle-prod.config.ts from the project root. This executes all pending .sql files in drizzle-pg/ in numerical order. For D1, use the push command against the D1 HTTP endpoint, which synchronizes the live database with the current TypeScript schema definitions.
Where are the Drizzle table definitions located?
Table definitions are located in src/db/ under specific schema modules: app.schema.ts (projects and keywords), audit.schema.ts (audits), better-auth-schema.ts (authentication), billing.schema.ts (subscriptions), gsc.schema.ts (Search Console), ga4.schema.ts (Analytics), sam.schema.ts (ads), and telemetry.schema.ts (metrics).
Can I use both SQLite and PostgreSQL simultaneously in the same deployment?
While the codebase supports both providers through configuration files, a single deployment typically targets one provider. The drizzle.config.ts (D1) and drizzle-prod.config.ts (PostgreSQL) are mutually exclusive at runtime, though the shared schema modules allow easy switching between providers during development or migration phases.
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 →