Migrate from Supabase
Supabase is a full application platform — PostgreSQL database, authentication, file storage, realtime subscriptions, edge functions, and an auto-generated REST API. DB9 replaces only the database layer. Everything else needs a separate solution or stays with Supabase.
This guide covers exporting your schema and data, importing into DB9, and replacing Supabase-specific database features in your application.
What DB9 Replaces and What It Does Not
Section titled “What DB9 Replaces and What It Does Not”DB9 replaces
Section titled “DB9 replaces”- PostgreSQL database — SQL queries, tables, indexes, functions, triggers
- Connection pooling endpoint — use application-side pooling instead
- pg_cron — DB9 has a built-in pg_cron extension
- pgvector — DB9 has built-in vector search with HNSW indexes and native embeddings
DB9 does not replace
Section titled “DB9 does not replace”| Supabase feature | What to use instead |
|---|---|
| Auth (GoTrue) | Third-party auth (Auth0, Clerk, Firebase Auth) or custom JWT |
| Storage (S3-backed) | AWS S3, GCS, or DB9’s fs9 extension for file-as-SQL workflows |
| Realtime (CDC + LISTEN/NOTIFY) | Polling, application WebSockets, or an external message queue |
| Edge Functions (Deno) | Cloudflare Workers, Vercel Functions, AWS Lambda |
| PostgREST (auto-generated API) | Build API routes in your framework (Next.js, Express, FastAPI) |
| Row-Level Security (RLS) | Enforce access control in your application layer |
| Dashboard and SQL editor | DB9 CLI (db9 db sql) and standard tools (psql, pgAdmin) |
If you only need to migrate the database and plan to rebuild or replace the other services, continue with this guide.
What Changes and What Stays the Same
Section titled “What Changes and What Stays the Same”Stays the same
Section titled “Stays the same”- SQL queries — DML, DDL, joins, CTEs, window functions, and subqueries work without changes.
- PostgreSQL drivers — node-postgres, psycopg, pgx, JDBC, and other pgwire drivers work with DB9.
- ORM compatibility — Prisma, Drizzle, SQLAlchemy, TypeORM, Sequelize, Knex, and GORM are tested.
- Common data types — TEXT, INTEGER, BIGINT, BOOLEAN, TIMESTAMPTZ, UUID, JSONB, arrays, vectors.
Changes
Section titled “Changes”| Area | Supabase | DB9 |
|---|---|---|
| Connection string | postgresql://postgres.[ref]:pass@pooler.supabase.com:6543/postgres | postgresql://tenant.role@pg.db9.io:5433/postgres |
| Port | 5432 (direct) or 6543 (pooled) | 5433 |
| Username | postgres or postgres.[ref] | tenant_id.role (e.g., a1b2c3d4e5f6.admin) |
| Connection pooling | Built-in Supavisor (transaction mode) | No built-in pooler — use application-side pooling |
| Row-Level Security | Supported and heavily used | Not supported |
| LISTEN/NOTIFY | Supported (powers Realtime) | Not supported |
| Extensions | 40+ (PostGIS, pg_graphql, pgsodium, etc.) | 9 built-in (http, vector, fs9, pg_cron, embedding, hstore, uuid-ossp, parquet, zhparser) |
| Table partitioning | Supported | Not supported |
| Replication | Logical replication supported | Not supported |
Review the Compatibility Matrix for the full list.
Prerequisites
Section titled “Prerequisites”- Access to your Supabase project (direct database connection string from Settings > Database)
pg_dumpinstalled locally (comes with PostgreSQL client tools)- DB9 CLI installed:
curl -fsSL https://get.db9.io | sh - A DB9 account:
db9 create --name my-appto create your target database
Step 1: Export from Supabase
Section titled “Step 1: Export from Supabase”Use the direct connection string from your Supabase dashboard (Settings > Database > Connection string > URI, not the pooled connection). pg_dump does not work over pooled connections.
Schema and data
Section titled “Schema and data”pg_dump --no-owner --no-privileges --no-comments \ -N auth -N storage -N realtime -N extensions -N supabase_functions -N supabase_migrations \ "postgresql://postgres.[your-ref]:[password]@db.[your-ref].supabase.co:5432/postgres?sslmode=require" \ > export.sqlThe -N flags exclude Supabase’s internal schemas. You want your application’s public schema (and any custom schemas), not Supabase’s platform tables.
Schema only
Section titled “Schema only”pg_dump --schema-only --no-owner --no-privileges \ -N auth -N storage -N realtime -N extensions -N supabase_functions -N supabase_migrations \ "postgresql://postgres.[your-ref]:[password]@db.[your-ref].supabase.co:5432/postgres?sslmode=require" \ > schema.sqlFlags explained:
--no-owner— omitsALTER ... OWNER TOstatements referencing Supabase-specific roles--no-privileges— omitsGRANT/REVOKEfor Supabase’s role hierarchy-N auth -N storage ...— excludes Supabase platform schemas
Step 2: Clean the Export
Section titled “Step 2: Clean the Export”Supabase exports commonly include features DB9 does not support. Review and remove:
Row-Level Security (most common)
Section titled “Row-Level Security (most common)”Supabase projects heavily use RLS. Remove all policy definitions:
# Find RLS statementsgrep -n "ROW LEVEL SECURITY\|CREATE POLICY\|ALTER POLICY" export.sqlRemove lines like:
-- Remove theseALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;CREATE POLICY "Users can view own posts" ON public.posts FOR SELECT USING (auth.uid() = user_id);You will need to enforce these access rules in your application layer instead (see Step 5).
Supabase-specific extensions
Section titled “Supabase-specific extensions”grep "CREATE EXTENSION" export.sqlKeep extensions that DB9 supports: uuid-ossp, hstore, vector (mapped to DB9’s built-in vector). Remove others — common Supabase extensions not in DB9 include pg_graphql, pgsodium, pg_net, pg_stat_statements, pgcrypto, PostGIS, pg_trgm.
If your application depends on pgcrypto for gen_random_uuid(), note that DB9 supports gen_random_uuid() as a built-in function — no extension needed.
Auth schema references
Section titled “Auth schema references”If your SQL references auth.uid() or auth.jwt() (common in RLS policies and triggers), remove those references:
grep -n "auth\.uid\|auth\.jwt\|auth\.role" export.sqlTriggers that reference Supabase internals
Section titled “Triggers that reference Supabase internals”grep -n "supabase_functions\|extensions\.\|realtime\." export.sqlRemove triggers that call into Supabase’s internal schemas.
Other unsupported features
Section titled “Other unsupported features”# Table partitioninggrep -i "PARTITION" export.sql
# Logical replicationgrep -i "PUBLICATION\|SUBSCRIPTION" export.sql
# Advisory locksgrep -i "advisory_lock" export.sqlStep 3: Create the DB9 Database
Section titled “Step 3: Create the DB9 Database”db9 create --name my-app --show-connection-stringReturns immediately with the connection string and credentials.
Step 4: Import into DB9
Section titled “Step 4: Import into DB9”Option A: CLI import (recommended for most databases)
Section titled “Option A: CLI import (recommended for most databases)”db9 db sql my-app -f export.sqlSuitable for databases up to the API import limits (50,000 rows or 16 MB per table).
Option B: Direct psql import (for larger databases)
Section titled “Option B: Direct psql import (for larger databases)”psql "$(db9 db status my-app --json | jq -r .connection_string)" -f export.sqlStreams SQL through pgwire without API size limits.
Option C: COPY for bulk data
Section titled “Option C: COPY for bulk data”Split schema and data imports for large databases:
# Import schemapsql "$(db9 db status my-app --json | jq -r .connection_string)" -f schema.sql
# Stream data from Supabase directly into DB9pg_dump --data-only --no-owner \ -N auth -N storage -N realtime -N extensions -N supabase_functions -N supabase_migrations \ "postgresql://postgres.[your-ref]:[password]@db.[your-ref].supabase.co:5432/postgres?sslmode=require" \ | psql "$(db9 db status my-app --json | jq -r .connection_string)"DB9 supports COPY in CSV and TEXT formats over pgwire.
Step 5: Update Your Application
Section titled “Step 5: Update Your Application”Connection string
Section titled “Connection string”Replace the Supabase connection string:
DATABASE_URL=postgresql://postgres.[ref]:[password]@db.[ref].supabase.co:5432/postgresDATABASE_URL=postgresql://a1b2c3d4e5f6.admin@pg.db9.io:5433/postgres?sslmode=requireReplace the Supabase client
Section titled “Replace the Supabase client”If you use @supabase/supabase-js for database queries, switch to a standard PostgreSQL driver:
import { createClient } from '@supabase/supabase-js';const supabase = createClient(url, anonKey);const { data } = await supabase.from('posts').select('*').eq('published', true);import pg from 'pg';const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 10 });const { rows: data } = await pool.query('SELECT * FROM posts WHERE published = true');Or use an ORM: Prisma, Drizzle, SQLAlchemy.
Replace Row-Level Security
Section titled “Replace Row-Level Security”Supabase RLS policies run inside the database. With DB9, enforce access control in your API or middleware:
// Before (Supabase RLS):// CREATE POLICY "own posts" ON posts FOR SELECT USING (auth.uid() = user_id);// const { data } = await supabase.from('posts').select('*');
// After (application-level):const userId = req.auth.userId; // From your auth middlewareconst { rows } = await pool.query( 'SELECT * FROM posts WHERE user_id = $1', [userId]);Every query that previously relied on RLS needs an explicit WHERE clause or middleware check.
Replace Realtime
Section titled “Replace Realtime”Supabase Realtime uses PostgreSQL’s LISTEN/NOTIFY (which DB9 does not support). Common alternatives:
- Polling — query the database on an interval for changes
- Application WebSockets — push changes from your API when writes happen
- External message queue — publish change events to Redis Pub/Sub, AWS SQS, or similar
Replace Storage
Section titled “Replace Storage”For file operations, consider:
- AWS S3 / GCS — direct replacement for Supabase Storage
- DB9 fs9 extension — store and query files directly in SQL (see fs9 guide)
Replace Edge Functions
Section titled “Replace Edge Functions”Supabase Edge Functions (Deno) have no DB9 equivalent. Use your preferred serverless platform (Cloudflare Workers, Vercel Functions, AWS Lambda) and connect to DB9 with a standard PostgreSQL driver.
Step 6: Validate
Section titled “Step 6: Validate”Check schema
Section titled “Check schema”db9 db dump my-app --ddl-onlyCompare with your original schema to confirm tables, indexes, and constraints were created.
Check row counts
Section titled “Check row counts”db9 db sql my-app -q "SELECT count(*) FROM posts"db9 db sql my-app -q "SELECT count(*) FROM users"Compare against Supabase.
Run your test suite
Section titled “Run your test suite”DATABASE_URL="$(db9 db status my-app --json | jq -r .connection_string)" npm testCommon test failures after migration
Section titled “Common test failures after migration”auth.uid()calls — these no longer exist. Replace with application-provided user IDs.- RLS-dependent queries — queries that relied on RLS returning filtered results now return all rows unless you add explicit
WHEREclauses. gen_random_uuid()without extension — DB9 supports this as a built-in function, but if your schema hasCREATE EXTENSION pgcrypto, remove it (DB9 does not have pgcrypto, but the function works natively).- SERIALIZABLE isolation — DB9 accepts the setting but runs as REPEATABLE READ.
Rollback Plan
Section titled “Rollback Plan”Keep your Supabase project running during migration. If you need to revert:
- Switch
DATABASE_URLback to the Supabase connection string. - If you need to export data created in DB9:
db9 db dump my-app -o db9-export.sqlpsql "postgresql://postgres.[ref]:[password]@db.[ref].supabase.co:5432/postgres?sslmode=require" \ -f db9-export.sqlThe db9 db dump command outputs plain SQL (up to 50,000 rows or 16 MB per table). For larger databases, use psql with COPY to stream individual tables.
Caveats
Section titled “Caveats”- Database only — DB9 does not replace Supabase Auth, Storage, Realtime, Edge Functions, or the PostgREST API. Plan replacements for each service you use.
- No zero-downtime migration — DB9 does not support logical replication. Plan a maintenance window for the cutover.
- RLS must move to application code — this is typically the largest refactoring effort. Audit every table that had RLS enabled.
- Extension gaps — PostGIS, pg_graphql, pgsodium, pg_net, and other Supabase extensions are not available. Check your
CREATE EXTENSIONstatements. - Dump size limits — The
db9 db sql -fAPI import has limits (50,000 rows, 16 MB per table). Use directpsqlfor larger databases. - No built-in REST API — Supabase auto-generates a REST API via PostgREST. With DB9, build your own API layer or use an ORM.
Next Pages
Section titled “Next Pages”- Compatibility Matrix — full list of supported and unsupported PostgreSQL features
- Connect — connection string format and authentication
- Migrate from Neon — Neon-specific migration guide
- Migrate from PostgreSQL — general PostgreSQL migration path
- fs9 Extension — file operations in SQL (alternative to Supabase Storage)
- Production Checklist — deployment readiness