Connect Django to Supabase Postgres
Use Supabase as the PostgreSQL database for a Django app: choose the direct connection or the Supavisor pooler, handle transaction-mode pooling with server-side cursors off, require SSL, keep Row Level Security from blocking Django, and run migrations.
Problem statement
Supabase gives you a hosted PostgreSQL database with a generous free tier, backups, and a web dashboard, which makes it an attractive database for Django apps hosted on Render, Railway, Fly.io, or Vercel. Supabase is designed around its own APIs and client libraries, though, so a Django app has to pick the right connection string (direct, session pooler, or transaction pooler), configure Django for transaction-mode pooling if it uses that, and make sure Supabase features such as Row Level Security do not interfere with the ORM.
- Copy a connection string from the Supabase dashboard (Connect button): direct, session pooler, or transaction pooler.
- Long-running servers (VPS, containers): use the direct connection (IPv6) or the session pooler (IPv4) with
CONN_MAX_AGE. - Serverless or many short-lived instances: use the transaction pooler on port 6543 with
DISABLE_SERVER_SIDE_CURSORS = TrueandCONN_MAX_AGE = 0. - Require SSL:
?sslmode=requireinDATABASE_URL. - Run migrations over a direct or session connection, not the transaction pooler.
- Leave Row Level Security off for Django-managed tables, or connect with a role that bypasses it.
Step-by-step solution
1. Pick the connection type
Supabase offers three endpoints:
- Direct connection (
db.<project>.supabase.co:5432): a real PostgreSQL connection; resolves to IPv6 by default. - Session pooler (Supavisor, port 5432 on the pooler host): one server connection per client session; works over IPv4.
- Transaction pooler (port 6543): server connections are shared between clients per transaction; best for serverless.
Hosts without IPv6 outbound connectivity (some PaaS and CI providers) cannot use the direct connection unless the IPv4 add-on is enabled; use the session pooler instead.
2. Configure Django for a long-running server
DATABASES = {"default": env.db("DATABASE_URL")} # postgresql://postgres.<ref>:<pw>@aws-0-eu-central-1.pooler.supabase.com:5432/postgres?sslmode=require
DATABASES["default"]["CONN_MAX_AGE"] = 60
DATABASES["default"]["CONN_HEALTH_CHECKS"] = True
3. Configure Django for the transaction pooler
DATABASES = {"default": env.db("DATABASE_URL")} # ...pooler.supabase.com:6543/postgres?sslmode=require
DATABASES["default"].update({
"CONN_MAX_AGE": 0,
"DISABLE_SERVER_SIDE_CURSORS": True,
})
In transaction mode, consecutive transactions from one client may use different server connections. Server-side cursors (used by .iterator() on PostgreSQL) and session-level state break across that boundary, so Django must disable them. Prepared statements are another session feature; psycopg 3 only prepares statements after repeated execution, and Supavisor supports named prepared statements in recent versions, but if you see "prepared statement does not exist" errors, set "OPTIONS": {"prepare_threshold": None} for psycopg 3.
4. Migrations
Run python manage.py migrate over the direct or session connection. Migrations use session-level features and long transactions that transaction pooling does not support well. In CI, set a separate MIGRATION_DATABASE_URL and use it for the migrate step.
5. Row Level Security and roles
Supabase encourages Row Level Security (RLS) for tables accessed through its auto-generated APIs. Tables created by Django migrations do not have RLS enabled unless you turn it on; if you do, policies apply to the role Django connects as, and queries may silently return no rows. Either keep Django's tables out of the exposed API schema and RLS, or connect with a role that has BYPASSRLS. Do not expose Django tables through Supabase's REST API unless you have written policies for them.
6. Backups and limits
The free tier pauses inactive projects and has limited connections and storage; production projects should use a paid plan with daily backups and point-in-time recovery. Watch the connection count in the dashboard as you scale workers.
Verification check: python manage.py migrate --check passes using the migration URL, python manage.py dbshell -c "show ssl;" returns on, and a request that iterates a large queryset works under the transaction pooler without cursor errors.
Explanation
Supabase runs standard PostgreSQL and fronts it with Supavisor, a connection pooler similar to PgBouncer. Django works with it like any PostgreSQL database; the choices that matter are about pooling. Session pooling behaves like a direct connection, while transaction pooling multiplexes many clients over few server connections, which suits serverless platforms that create many short-lived processes but requires Django to avoid session-scoped features. Supabase's API and RLS layers are optional for Django apps and should be configured consciously rather than left to defaults.
Edge cases / notes
- IPv6. The direct hostname is IPv6-only by default; check your host's outbound IPv6 support.
- Extensions such as
pg_trgm,postgis, andvectorare available; enable them in the dashboard or a migration with sufficient privileges. - Auth. Using Supabase Auth with Django's own auth is possible but means two user systems; most Django apps keep Django auth.
- Project pausing on the free tier makes the first request after a pause fail; do not use it for production.
Next steps
- Compare with another serverless Postgres: Connect Django to Neon Serverless Postgres.
- Understand pooling modes with Set Up PgBouncer for Django.
FAQ
Can Django use Supabase as its database?
Yes. Supabase is PostgreSQL; use its connection string with Django's PostgreSQL backend.
Which Supabase connection string should Django use?
Direct or session pooler for long-running servers; the transaction pooler (port 6543) for serverless.
Why do I get cursor errors with Supabase?
The transaction pooler does not support server-side cursors. Set DISABLE_SERVER_SIDE_CURSORS = True.
Should Django tables use Row Level Security?
Not unless you expose them through Supabase's API. RLS applies to Django's connection role too.