What Supabase AI actually is
Supabase is an open-source backend platform built on Postgres. You get a managed database, authentication, file storage, realtime channels, and edge functions. Supabase AI is the umbrella term for a set of AI features layered on top of that stack. Stripped of marketing, it is three concrete things working together.
First, there is the AI assistant inside the Supabase dashboard SQL editor. You can describe what you want in natural language and it generates SQL, schema, or Postgres functions. It runs against your real schema context, which makes it more useful than a generic SQL generator.
Second, there is native vector search through the pgvector extension. Postgres already supports the vector data type, distance operators, and index types. Supabase exposes this through the same SQL and REST interfaces you already use for relational data.
Third, there are AI helpers for building retrieval augmented generation pipelines. Edge functions ship with example code for calling OpenAI, storing embeddings, and returning matched documents. The supabase-js client includes vector helpers in the current version.
What it is not, despite how it gets pitched, is a purpose-built vector database. It is Postgres with vector capabilities bolted in. That distinction matters because the trade-offs of Postgres carry over. You get transactional consistency, row level security, and SQL joins on vectors. You do not get the raw throughput of a dedicated vector engine at billion-scale.
Setup and authentication
You need three things before any of this works: a Supabase project, the pgvector extension enabled, and credentials your application can use.
Create a project from the dashboard or the CLI. The CLI route is faster for developers.
npm install supabase --save-dev
npx supabase login
nxt supabase init
The init command creates a supabase directory with config.toml, migrations folder, and seed file. Start a local stack with npx supabase start. This spins up Postgres, GoTrue, PostgREST, and the studio in Docker. Local development matches production closely enough that you can iterate without burning cloud credits.
Enable pgvector in your database. Run this in the SQL editor or as a migration.
create extension if not exists vector;
In a migration file under supabase/migrations, this becomes part of your versioned schema.
Authentication uses two keys. The anon key is a JWT that respects row level security. It is safe to expose in client code. The service_role key bypasses RLS and must stay on the server. Edge functions and backend workers use it.
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
)
For server-side embedding generation, swap in the service_role key. Never ship it to the browser.
First working example
This is the smallest end-to-end RAG slice you can build. A documents table, an embedding column, and a similarity query.
Create the schema.
create table documents (
id bigserial primary key,
content text not null,
embedding vector(1536),
metadata jsonb default '{}'::jsonb
);
create index on documents
using hnsw (embedding vector_cosine_ops);
The 1536 dimension matches OpenAI text-embedding-3-small and ada-002. If you use a different model, change the dimension. The HNSW index trades write speed for fast approximate search. For datasets in the low millions of rows it is the right default.
Generate an embedding and insert it. This example uses Node and the OpenAI client.
import OpenAI from 'openai'
import { createClient } from '@supabase/supabase-js'
const openai = new OpenAI()
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY
)
async function embed(text) {
const res = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text
})
return res.data[0].embedding
}
async function insertDocument(content) {
const embedding = await embed(content)
const { error } = await supabase
.from('documents')
.insert({ content, embedding })
if (error) throw error
}
Pass the embedding as a plain array. The PostgREST layer serializes it to the vector type.
Query for similar documents.
async function search(query, matchCount = 5) {
const embedding = await embed(query)
const { data, error } = await supabase.rpc('match_documents', {
query_embedding: embedding,
match_count: matchCount
})
if (error) throw error
return data
}
The RPC calls a Postgres function that does the actual distance calculation.
create or replace function match_documents(
query_embedding vector(1536),
match_count int default 5
) returns table (
id bigint,
content text,
similarity float
) language sql stable as $$
select
id,
content,
1 - (embedding <=> query_embedding) as similarity
from documents
order by embedding <=> query_embedding
limit match_count;
$$;
The <=> operator is cosine distance. Other operators are <-> for L2 and <#> for inner product. Pick the one that matches your embedding model’s training objective.
That is a working RAG pipeline. Ingest, embed, store, search.
Key settings that matter
Most people ignore these and then hit walls later.
Vector dimension must match your model exactly. OpenAI’s text-embedding-3-small produces 1536 dimensions. Cohere’s embed-english-v3.0 produces 1024. Mixing them in the same column breaks queries silently. If you change models, write a migration that drops and recreates the column.
Index type changes the performance profile. HNSW gives fast queries with higher memory use and slower inserts. IVFFlat is faster to build and lighter on memory but needs a trained centroid set. For tables under 100k rows you can skip the index entirely and Postgres will still scan quickly. For anything larger, HNSW with cosine ops is the right starting point.
Row level security applies to vectors the same way it applies to text. If users should only see their own documents, write a policy.
alter table documents enable row level security;
create policy "users read own documents"
on documents for select
using (auth.uid() = (metadata->>'user_id')::uuid);
Without this, the anon key exposes every embedding to every visitor.
Pro plan limits matter at scale. The free tier has a 500MB database cap and pauses after a week of inactivity. Pro raises storage into the multi-GB range and adds daily backups. Vector indexes eat memory fast, so plan capacity accordingly.
Edge function cold starts add latency to the first request after idle. If your embedding pipeline runs in an edge function, expect 200 to 800ms on cold calls in the range typical for serverless platforms at this tier. Keep a warm worker or run heavy work on a long-lived backend.
Connection pooling is on by default but worth knowing about. Supabase uses PgBouncer in transaction mode. Vector queries that hold a cursor across many round trips can fight the pooler. For long-running similarity jobs, connect directly to the database rather than through PostgREST.
Where it shines
Multi-tenant RAG applications are the sweet spot. Each user has documents, embeddings, and chat history in the same database. Row level security keeps tenants isolated without extra application logic. You write one query and Postgres enforces the boundary.
Real-time features pair naturally with vector data. You can subscribe to inserts on the documents table and stream new context to a chat client. A dedicated vector store cannot do that without a separate pub/sub layer.
Teams that already use Supabase for auth and storage get vector search for free in the operational sense. No second vendor, no second auth flow, no second billing relationship. For a small product team this is the difference between shipping in a week and shipping in a month.
Postgres extensions are first-class. You can combine pgvector with pg_trgm for hybrid keyword and semantic search, or with PostGIS for geo-aware retrieval. Few vector databases let you join embeddings against relational filters in the same query plan.
The AI assistant in the SQL editor is genuinely useful for writing the boilerplate of vector functions. Describe the schema and the retrieval pattern, and it produces a working function you can adapt. For developers who do not write Postgres daily this saves real time.
Where it fails
It is not a dedicated vector database. Pinecone, Weaviate, and Qdrant are tuned for billion-vector workloads with GPU-accelerated indexes. Supabase tops out somewhere in the tens of millions of vectors before performance degrades noticeably. If your corpus is the entire Common Crawl, this is the wrong tool.
There is no native reranker or hybrid scoring. You build that yourself with a second query or an external service. The platform gives you raw distance operators and leaves ranking strategy to you.
Cold start latency on edge functions is real. Embedding generation that takes 150ms on a warm server can take 600ms on a cold one. For latency-sensitive chat products this is visible to users.
The AI assistant has guardrails. It will refuse some schema changes and is conservative about destructive operations. For greenfield work this is fine. For complex migrations you still need a human reviewer.
Cost climbs with embedding volume. Each insert triggers a model call, which is billed by tokens. Bulk ingestion of large corpora can run into the hundreds of dollars in API fees even when the database storage is cheap. Budget for that separately.
Observability is thin compared to dedicated vector platforms. You get Postgres query stats and Supabase logs. You do not get vector-specific dashboards showing recall, latency percentiles by index, or embedding drift over time. Build your own monitoring if those matter.
Practical workflow pattern
Here is the setup that works for a small team shipping a RAG product on Supabase.
Develop locally with the Supabase CLI. Run supabase start to bring up the full stack. Write schema changes as numbered migration files under supabase/migrations. Seed sample data with the seed script. The local studio at port 54323 lets you inspect tables and run SQL without touching production.
Generate embeddings in a background worker, not in the request path. A Node or Python worker reads from a queue, calls the embedding model, and writes to Supabase using the service_role key. This keeps user-facing requests fast and lets you retry failures without timing out a user.
Store source documents and embeddings in the same row. Splitting them across tables forces joins on every retrieval and complicates row level security. One row per chunk with metadata in a jsonb column is the right shape for most RAG apps.
Version your embedding model. Add a column called embedding_model and filter on it during retrieval. When you upgrade to a new model, re-embed in batches and track progress through that column. Mixing models in production is the most common silent failure in vector systems.
Deploy edge functions for the retrieval path. The function takes a query, embeds it, calls the match function, and returns results. Edge deployment puts the function close to the database and avoids round trips through your origin server.
Monitor with Supabase logs plus an external tool. Watch for slow queries on the documents table, especially those involving the vector column. Set alerts on p95 latency for the match function. Track embedding generation costs in your model provider dashboard.
This pattern keeps the operational surface small. One database, one auth system, one deployment target. The trade-off is that you give up the raw performance ceiling of a dedicated vector engine, which is fine until it is not.
Enterprise DNA put together a free field guide on exactly this: the full Claude ecosystem, Claude Code, and how to roll agents out without breaking things. Get the guide.