Supabase wraps Postgres with auth, real-time subscriptions, storage, and auto-generated APIs. This skill covers the database layer — schema design, RLS, migrations, indexing, and query optimization.
When to use
Designing schema for a new Supabase-backed application
Adding Row Level Security policies to protect data per-user or per-org
Writing migrations that are safe for zero-downtime deployments
Diagnosing slow queries and adding appropriate indexes
Setting up connection pooling for serverless workloads
Optimizing Postgres for high-read, low-write web app patterns
Implementing soft deletes, audit trails, or temporal data patterns
When NOT to use
Graph-shaped data that needs recursive traversals (consider a graph DB)
Time-series at extreme write volume (consider TimescaleDB or ClickHouse)
Full-text search as the primary access pattern (consider a dedicated search engine)
Blob storage (use Supabase Storage or S3, not bytea columns)
When the app only needs a key-value store (use Redis or KV)
Core concepts
Connection pooling
Supabase offers server-side connection poolers to handle the high-concurrency, short-lived connections typical of edge and serverless environments. Historically this included PgBouncer; Supabase now provides Supavisor as a managed pooler with explicit guidance in the docs (see Supavisor FAQ). Pooler behavior you should know:
Ports and modes: transaction-mode pooling is typically exposed on port 6543 and is recommended for serverless/edge workloads. Session-mode (full session semantics) is available on port 5432 or can be enabled/configured via the dashboard (see Supavisor docs). (Source: Supabase Supavisor FAQ)
Use case: Transaction mode lets the pooler reuse server connections for short-lived transactions, avoiding the cost of opening Postgres backend processes for every request.
Caution: Transaction-mode pooling (PgBouncer or Supavisor in transaction mode) breaks session-level features: prepared statements, session-local GUCs, LISTEN/NOTIFY scoping, and anything that expects a stable, exclusive backend per client. Use session mode when you need these features. (Source: Supavisor FAQ)
Pool sizing: Supabase dashboard exposes Max Client Connections and Default Pool Size. Tune both app-side pools and the server-side pooler to avoid saturating the database.
RLS policies run as implicit WHERE clauses on every query. Supabase exposes auth.uid() and auth.jwt() as helper functions inside policies.
Extensions
Supabase pre-installs many popular Postgres extensions and lets you enable additional SQL extensions via the dashboard or SQL editor. Upgrading an extension may require initiating a software upgrade from the Infrastructure Settings. Use extensions when they materially simplify an implementation (e.g., PostGIS, pgcrypto, pg_ivm for incremental view maintenance). (Source: Supabase extensions docs; pg_ivm noted in Postgres signals)
Migration safety
Migrations in production must be backwards-compatible. Never rename a column — add a new one, backfill, switch code, then drop the old column.
Workflow
Step 1 — Define schema with migrations
-- supabase/migrations/001_create_projects.sql
CREATE TABLE projects (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
name text NOT NULL CHECK (char_length(name) BETWEEN 1 AND 200),
slug text NOT NULL UNIQUE,
description text,
is_archived boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_projects_owner ON projects (owner_id);
CREATE INDEX idx_projects_slug ON projects (slug);
-- Updated-at trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_projects_updated_at
BEFORE UPDATE ON projects
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
Step 2 — Add RLS policies
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Users can read their own projects
CREATE POLICY "Users read own projects"
ON projects FOR SELECT
USING (owner_id = auth.uid());
-- Users can insert projects they own
CREATE POLICY "Users insert own projects"
ON projects FOR INSERT
WITH CHECK (owner_id = auth.uid());
-- Users can update their own non-archived projects
CREATE POLICY "Users update own projects"
ON projects FOR UPDATE
USING (owner_id = auth.uid() AND NOT is_archived)
WITH CHECK (owner_id = auth.uid());
-- Users can soft-delete (archive) their own projects
CREATE POLICY "Users archive own projects"
ON projects FOR UPDATE
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid() AND is_archived = true);
Step 3 — Optimize with targeted indexes
-- Partial index for active (non-archived) projects
CREATE INDEX idx_projects_active
ON projects (owner_id, created_at DESC)
WHERE NOT is_archived;
-- GIN index for full-text search on name + description
CREATE INDEX idx_projects_search
ON projects
USING GIN (to_tsvector('english', coalesce(name, '') || ' ' || coalesce(description, '')));
Step 4 — Query with the Supabase client
import { createClient } from "@supabase/supabase-js";
import type { Database } from "@/lib/database.types";
const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
async function listActiveProjects(userId: string) {
const { data, error } = await supabase
.from("projects")
.select("id, name, slug, created_at")
.eq("owner_id", userId)
.eq("is_archived", false)
.order("created_at", { ascending: false })
.limit(50);
if (error) throw new Error(`listActiveProjects: ${error.message}`);
return data;
}
Step 5 — Run and verify migrations
# Local development
supabase db reset
supabase db diff --use-migra
# Production (via CI)
supabase db push --linked
Examples
Multi-tenant RLS with org membership
CREATE TABLE org_members (
org_id uuid REFERENCES orgs(id) ON DELETE CASCADE,
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
role text NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
PRIMARY KEY (org_id, user_id)
);
CREATE POLICY "Org members read org data"
ON org_data FOR SELECT
USING (
org_id IN (
SELECT org_id FROM org_members WHERE user_id = auth.uid()
)
);
Safe column migration (zero-downtime)
-- Step 1: Add new column
ALTER TABLE projects ADD COLUMN display_name text;
-- Step 2: Backfill (batched to avoid long locks)
UPDATE projects SET display_name = name WHERE display_name IS NULL;
-- Step 3: (deploy code that reads display_name with fallback to name)
-- Step 4: Make non-null after code is deployed
ALTER TABLE projects ALTER COLUMN display_name SET NOT NULL;
ALTER TABLE projects ALTER COLUMN display_name SET DEFAULT '';
-- Step 5: (deploy code that only reads display_name, drop old references)
Decision tree
New table or column?
├── Add migration file in supabase/migrations/
├── Need per-user isolation? → Add RLS policy
├── Queried frequently by a non-PK column? → Add index
├── Column holds large text searched often? → Add GIN index with to_tsvector
└── Column needs uniqueness? → Add UNIQUE constraint in migration
Slow query?
├── Run EXPLAIN ANALYZE
├── Sequential scan on large table? → Add index on filter/join columns
├── Index exists but not used? → Check for type mismatch or function wrapping
├── Lots of dead tuples? → Run VACUUM ANALYZE
└── Still slow? → Consider materialized view or denormalization
Edge cases and gotchas
RLS is bypassed by service_role key: The service_role key skips all RLS. Never expose it to the client. Use anon key in browsers.
Transaction-mode pooling breaks prepared statements: PgBouncer or Supavisor running in transaction mode will break server-side prepared statements and some ORMs' expectations (e.g., asyncpg). Use session mode or application-side workarounds when your driver depends on prepared statements. (Source: Supavisor FAQ and community reports)
Migrations with locks: ALTER TABLE ... ADD COLUMN with a DEFAULT on Postgres < 11 locks the table. On 11+, it's instant for non-volatile defaults. ADD COLUMN NOT NULL without a default still locks.
UUID primary keys: Use gen_random_uuid() (Postgres 13+) over uuid_generate_v4() (requires extension). UUIDs are great for distributed systems but make range scans slower than integer IDs.
Cascading deletes: ON DELETE CASCADE is convenient but dangerous on large tables — it can lock rows across multiple tables. Prefer soft deletes for user-facing data.
Real-time depends on RLS: Supabase Realtime subscriptions respect RLS policies. If RLS is misconfigured, clients receive no events — not an error, just silence.
Supabase Log Drains (Pro): Supabase now offers Log Drains on the Pro tier so you can export Postgres, Auth, Storage, Edge Functions, Realtime, and API logs to Datadog, Sentry, Grafana Loki, Axiom, S3 or a custom endpoint. This is useful to correlate database query errors, auth failures, and Edge Function traces in your existing observability tools. (Source: Supabase blog/changelog)
Supabase CLI diff limitations: supabase db diff may miss some changes (triggers, functions). Always review generated SQL before applying.
Evaluation criteria
Criteria
Target
Query P95 latency
< 100 ms for indexed queries
Migration rollback plan
Every migration has a documented reverse
RLS coverage
100% of user-facing tables have RLS enabled
Index bloat
Unused indexes identified and dropped
Connection pool usage
< 80% of pool capacity under peak load
Dead tuple ratio
< 5% (autovacuum tuned)
Research-backed changes
This refresh pulled recent signals and updated guidance for poolers, observability, and extensions.
Fresh signals incorporated
Supabase launched Log Drains on Pro to export Postgres and service logs to external observability tools (Supabase blog, Mar 5, 2026).
Supabase's Supavisor connection-pooler docs and FAQ clarify port/mode semantics (transaction vs session) and caveats for transaction-mode pooling (Supabase docs, Feb 2026).
Postgres ecosystem signals (pg_ivm 1.14 and other extension updates) indicate growing availability of incremental view maintenance and other extensions; Supabase supports many extensions and provides an extensions dashboard to enable them.
What changed
Rewrote the Connection pooling section to include Supavisor and clarify port/mode behavior and impacts on prepared statements.
Added an Extensions subsection explaining extension upgrades and where to enable them.
Added Log Drains as an operational note under gotchas/observability.
Next edits
Tighten one ambiguous instruction.
Swap in a higher-signal source if Supabase publishes a pooler migration guide or PgBouncer→Supavisor migration docs.
Activity
ActiveDaily · 9:00 AM4 sources
Automation & run history
Automation status and run history. Only the owner can trigger runs or edit the schedule.
Check Supabase blog for new Postgres extensions, RLS pattern updates, and connection-pooling changes. Scan Neon blog for serverless Postgres features. Monitor Postgres Weekly for query-optimization techniques. Update schema-design and indexing guidance.
Latest refresh trace
Reasoning steps, source results, and the diff that landed.
Apr 11, 2026 · 9:28 AM
triggerAutomation
editoropenai/gpt-5-mini
duration115.4s
statussuccess
web searches3
sources discovered+1
Revision: v8
Updated pooling guidance with Supavisor ports/modes, added pooler monitoring metrics and observability checklist; noted pg_textsearch v1.0 availability and new Postgres 19 maintenance/planner features; added work_mem gotcha and Log Drains operational note.
- Rewrote Connection pooling to reference Supavisor docs and added metrics to monitor pooler health
- Added Postgres features to watch (REPACK, pg_plan_advice, COPY ... WITH JSON)
- Documented pg_textsearch v1.0 as a new extension signal and reminded to verify availability in Supabase
- Added Log Drains operational note and an observability checklist
- Added a gotcha on work_mem / per-query memory issues
Key facts (Supavisor docs, last edited 2026-04-08):
−- Ports and modes: transaction-mode pooling is typically exposed on port 6543 and is recommended for serverless/edge workloads; session-mode (full session semantics) is available on port 5432 (direct/session) and is needed for prepared statements, session-local GUCs, LISTEN/NOTIFY scoping, or any feature that depends on a stable backend per client. (Source: Supavisor FAQ)
+- Ports and modes: transaction-mode pooling is exposed on port 6543 and is recommended for serverless/edge workloads; session-mode (full session semantics) is available on port 5432 (direct/session) and is needed for prepared statements, session-local GUCs, LISTEN/NOTIFY scoping, or any feature that depends on a stable backend per client. (Source: Supavisor FAQ & Troubleshooting pages,2026-04-08)
- Use case: Transaction mode lets the pooler reuse server connections for short-lived transactions, avoiding the cost of creating a Postgres backend process for every client request.
- Caveats: Transaction-mode pooling breaks session-level features including prepared statements, some server-side cursors, session-local configuration, and the scoping semantics of LISTEN/NOTIFY. If your application or ORM relies on these, use a session-mode connection or adapt your client code. (Source: Supavisor FAQ)
- Pool sizing: Tune both application-side pool settings and the server-side pooler to avoid saturating the database. Supabase exposes Max Client Connections and pool-size controls in the dashboard; monitor pool saturation and queueing under load.
@@ −42 +42 @@
- Test your workload under realistic concurrency; transaction pooling reduces connection counts but introduces query queuing when demand exceeds available pooled server connections.
- Some libraries or drivers (e.g., asyncpg) assume prepared statements. If you run into errors, switch to session-mode or disable server-side prepared statements.
+- Metrics to monitor for pooler health: database max_connections (SHOW max_connections), current active connections (pg_stat_activity), pooler pool size and allocated server connections (as reported in Supabase dashboard), and client queue length / avg queue wait time. Monitor queueing and latency to detect saturation early (Source: Supavisor docs).
### Row Level Security (RLS)
@@ −54 +55 @@
- Auto-enable RLS: Supabase documents an event-trigger pattern you can install to enable RLS for newly created tables automatically (useful for platform workflows that create schemas/tables programmatically).
- Beware the service_role key: it bypasses RLS entirely. Never expose it to untrusted clients.
−Example policy (unchanged):
+Example policy:
CREATE POLICY "Users read own projects"
ON projects FOR SELECT
@@ −66 +67 @@
Recent ecosystem signals to watch:
+- pg_textsearch v1.0 (BM25 scoring) reached production readiness in March 2026 and provides BM25 relevance scoring implemented at the Postgres page level with parallel index builds and disk-backed segments. It is a candidate when you need relevance-ranked search without an external engine; always verify availability in your Supabase project before relying on it. (Source: pg_textsearch v1.0 release notes, 2026-03-27; Postgres ecosystem signals)
−- pg_textsearchv1.0(BM25scoring)reachedproduction-readinessin2026;itprovidesBM25relevancescoringimplementedatthePostgrespagelevelandsupportsparallelindexbuildsanddisk-backedsegments.Consideritwhenyouneedrelevance-rankedsearch without switching to an external engine (source: Postgres Weekly, Apr 2026).
−- pg_ivm continues to be a practical option for incremental materialized views (signals and updates in the ecosystem indicate active releases). Supabase exposes an extensions dashboard where you can enable supported extensions — check the project dashboard or docs before relying on non-standard extensions.
Extension upgrade note: upgrading an extension may require explicit intervention (or coordinating with Supabase infrastructure) — verify the extension version and test any upgrade on a staging clone.
Research engine
Supabase Database Patterns now treats its source set as a research system: canonical feeds for concrete deltas, index-like sources for discovery, and query hints for ranking.
4 sourcesRank 8Quality 80
Why this is featured
Supabase Database Patterns has unusually strong source quality and broad utility, so it deserves prominent placement.
Discovery process
1. Track canonical signals
Monitor 4 feed-like sources for release notes, changelog entries, and durable upstream deltas.
2. Discover net-new docs and leads
Scan 0 discovery-oriented sources such as docs indexes and sitemaps, then rank extracted links against explicit query hints instead of trusting nav order.
3. Transplant from trusted upstreams
Keep the skill grounded in trusted source deltas even when there is no direct upstream skill pack to transplant from.
4. Keep the sandbox honest
Ship prompts, MCP recommendations, and automation language that can actually be executed in Loop's sandbox instead of abstract advice theater.
Core concepts: Connection poolingCore concepts: ExtensionsEdge cases and gotchasResearch-backed changes
status
success
triggerAutomation
editoropenai/gpt-5-mini
duration115.4s
Diff▶
+10−7
+Generated: 2026-04-07T09:26:35.047Z
+Summary: Updated connection pooling guidance to reflect Supavisor and port/mode semantics, added Log Drains (Pro) as an observability note, and included an Extensions subsection to explain enabling/upgrading extensions. Changes trace to Supabase docs/changelog and Postgres ecosystem signals.
+What changed: - Connection pooling section rewritten to include Supavisor, port/mode semantics, and prepared-statement caveats
+- New Extensions subsection with enabling/upgrading guidance
+- Added Supabase Log Drains note under Edge cases and gotchas
+- Updated Research-backed changes and Fresh signals lists
−Generated:2026-04-05T09:54:36.528Z
+Body changed:yes
−Summary: Supabase Database Patterns agent run was interrupted: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Pur
−What changed: Agent crashed mid-run after 0 search(es). (agent error: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Purchase credits at htt)
+- Track Supabase Supavisor release notes and pooler migration guide for explicit migration steps
−- Re-runaftertheissueisresolved.
+- Benchmark app performance differences between Supavisor transactionvssessionmodeundercommonORMs
−- Add a higher-signal source.
−- Check gateway credits or rate limits.
Signals:
- pg_textsearch 1.0 brings production-ready BM25 to Postgres (Postgresweekly rss)
- Moving one word in a query unlocks a 32x speedup (Postgresweekly rss)
Update history4▶
Apr 7, 20264 sources
Updated connection pooling guidance to reflect Supavisor and port/mode semantics, added Log Drains (Pro) as an observability note, and included an Extensions subsection to explain enabling/upgrading extensions. Changes trace to Supabase docs/changelog and Postgres ecosystem signals.
Apr 5, 20264 sources
Supabase Database Patterns agent run was interrupted: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Pur
Apr 3, 20264 sources
Supabase Database Patterns agent run was interrupted: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Pur
Apr 1, 20260 sources
Supabase Database Patterns held steady in this pass. No fresh remote signal landed, so the skill kept its last known posture.