Skip to content
Discord Get Started

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.

  • 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
Supabase featureWhat 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 editorDB9 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.

  • 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.
AreaSupabaseDB9
Connection stringpostgresql://postgres.[ref]:pass@pooler.supabase.com:6543/postgrespostgresql://tenant.role@pg.db9.io:5433/postgres
Port5432 (direct) or 6543 (pooled)5433
Usernamepostgres or postgres.[ref]tenant_id.role (e.g., a1b2c3d4e5f6.admin)
Connection poolingBuilt-in Supavisor (transaction mode)No built-in pooler — use application-side pooling
Row-Level SecuritySupported and heavily usedNot supported
LISTEN/NOTIFYSupported (powers Realtime)Not supported
Extensions40+ (PostGIS, pg_graphql, pgsodium, etc.)9 built-in (http, vector, fs9, pg_cron, embedding, hstore, uuid-ossp, parquet, zhparser)
Table partitioningSupportedNot supported
ReplicationLogical replication supportedNot supported

Review the Compatibility Matrix for the full list.

  • Access to your Supabase project (direct database connection string from Settings > Database)
  • pg_dump installed locally (comes with PostgreSQL client tools)
  • DB9 CLI installed: curl -fsSL https://get.db9.io | sh
  • A DB9 account: db9 create --name my-app to create your target database

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.

Terminal
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.sql

The -N flags exclude Supabase’s internal schemas. You want your application’s public schema (and any custom schemas), not Supabase’s platform tables.

Terminal
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.sql

Flags explained:

  • --no-owner — omits ALTER ... OWNER TO statements referencing Supabase-specific roles
  • --no-privileges — omits GRANT/REVOKE for Supabase’s role hierarchy
  • -N auth -N storage ... — excludes Supabase platform schemas

Supabase exports commonly include features DB9 does not support. Review and remove:

Supabase projects heavily use RLS. Remove all policy definitions:

Terminal
# Find RLS statements
grep -n "ROW LEVEL SECURITY\|CREATE POLICY\|ALTER POLICY" export.sql

Remove lines like:

SQL
-- Remove these
ALTER 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).

Terminal
grep "CREATE EXTENSION" export.sql

Keep 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.

If your SQL references auth.uid() or auth.jwt() (common in RLS policies and triggers), remove those references:

Terminal
grep -n "auth\.uid\|auth\.jwt\|auth\.role" export.sql

Triggers that reference Supabase internals

Section titled “Triggers that reference Supabase internals”
Terminal
grep -n "supabase_functions\|extensions\.\|realtime\." export.sql

Remove triggers that call into Supabase’s internal schemas.

Terminal
# Table partitioning
grep -i "PARTITION" export.sql
# Logical replication
grep -i "PUBLICATION\|SUBSCRIPTION" export.sql
# Advisory locks
grep -i "advisory_lock" export.sql
Terminal
db9 create --name my-app --show-connection-string

Returns immediately with the connection string and credentials.

Section titled “Option A: CLI import (recommended for most databases)”
Terminal
db9 db sql my-app -f export.sql

Suitable 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)”
Terminal
psql "$(db9 db status my-app --json | jq -r .connection_string)" -f export.sql

Streams SQL through pgwire without API size limits.

Split schema and data imports for large databases:

Terminal
# Import schema
psql "$(db9 db status my-app --json | jq -r .connection_string)" -f schema.sql
# Stream data from Supabase directly into DB9
pg_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.

Replace the Supabase connection string:

Diff
DATABASE_URL=postgresql://postgres.[ref]:[password]@db.[ref].supabase.co:5432/postgres
DATABASE_URL=postgresql://a1b2c3d4e5f6.admin@pg.db9.io:5433/postgres?sslmode=require

If you use @supabase/supabase-js for database queries, switch to a standard PostgreSQL driver:

Diff
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.

Supabase RLS policies run inside the database. With DB9, enforce access control in your API or middleware:

TypeScript
// 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 middleware
const { 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.

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

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)

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.

Terminal
db9 db dump my-app --ddl-only

Compare with your original schema to confirm tables, indexes, and constraints were created.

Terminal
db9 db sql my-app -q "SELECT count(*) FROM posts"
db9 db sql my-app -q "SELECT count(*) FROM users"

Compare against Supabase.

Terminal
DATABASE_URL="$(db9 db status my-app --json | jq -r .connection_string)" npm test
  • 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 WHERE clauses.
  • gen_random_uuid() without extension — DB9 supports this as a built-in function, but if your schema has CREATE 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.

Keep your Supabase project running during migration. If you need to revert:

  1. Switch DATABASE_URL back to the Supabase connection string.
  2. If you need to export data created in DB9:
Terminal
db9 db dump my-app -o db9-export.sql
psql "postgresql://postgres.[ref]:[password]@db.[ref].supabase.co:5432/postgres?sslmode=require" \
-f db9-export.sql

The 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.

  • 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 EXTENSION statements.
  • Dump size limits — The db9 db sql -f API import has limits (50,000 rows, 16 MB per table). Use direct psql for 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.