Skip to content

Building AI-Powered Apps with Supabase and pgvector

Updated Sep 2026

verified on Ubuntu 26.04 · Aug 2026
We earn commissions when you shop through the links below. Full disclosure →

Use Supabase's pgvector extension to store and search AI embeddings, build semantic search, and create RAG applications with your own data.

Before you start
  • Supabase running on a VPS (see Self-Host Supabase)
  • Basic understanding of SQL and embeddings
  • An embedding model (OpenAI, Ollama, or local)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

Why Supabase + AI?

Supabase is an open-source Firebase alternative with a Postgres database, auth, storage, and edge functions. Adding pgvector (a Postgres extension for vector search) turns it into a powerful backend for AI applications — you can store embeddings alongside your relational data, search by semantic similarity, and build retrieval-augmented generation (RAG) applications without a separate vector database.

The appeal is unified data layer. Instead of maintaining a separate vector database (Pinecone, Weaviate, etc.) alongside your relational database, everything lives in Postgres. Your embeddings are queryable with standard SQL, joinable with your other data, and backed by Postgres's reliability.

This guide assumes you already have Supabase running. If not, set up a self-hosted instance first, then return here to add AI capabilities.

Enable pgvector

pgvector comes pre-installed with Supabase, but you need to enable it:

-- In the Supabase SQL editor or via psql
CREATE EXTENSION IF NOT EXISTS vector;

Verify it's enabled:

SELECT * FROM pg_extension WHERE extname = 'vector';

You should see a row confirming the extension is installed.

Create a table for embeddings

Create a table to store vectors alongside your data:

CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  metadata JSONB DEFAULT '{}',
  embedding VECTOR(1536) NOT NULL,  -- 1536 for OpenAI ada-002, adjust for your model
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Create an index for fast similarity search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);  -- adjust lists based on your data size

The embedding column stores the vector. The dimension (1536) must match your embedding model's output. Common dimensions:

  • OpenAI text-embedding-3-small: 1536
  • OpenAI text-embedding-3-large: 3072
  • Ollama nomic-embed-text: 768
  • Ollama mxbai-embed-large: 1024
Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Generate embeddings

You need to generate embeddings for your content. Here's how with different backends:

Using OpenAI:

-- Create a function to generate embeddings via OpenAI API
CREATE OR REPLACE FUNCTION generate_embedding(text_content TEXT)
RETURNS VECTOR AS $$
DECLARE
  response JSONB;
BEGIN
  SELECT content INTO response
  FROM net.http_post(
    url := 'https://api.openai.com/v1/embeddings',
    headers := jsonb_build_object(
      'Authorization', 'Bearer ' || current_setting('app.settings.openai_api_key'),
      'Content-Type', 'application/json'
    ),
    body := jsonb_build_object(
      'model', 'text-embedding-3-small',
      'input', text_content
    )
  );
  
  RETURN (response->'data'->0->'embedding')::VECTOR;
END;
$$ LANGUAGE plpgsql;

Using Ollama (via Edge Function):

// supabase/functions/generate-embedding/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";

serve(async (req) => {
  const { text } = await req.json();
  
  const response = await fetch("http://host.docker.internal:11434/api/embeddings", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "nomic-embed-text",
      prompt: text,
    }),
  });
  
  const { embedding } = await response.json();
  
  return new Response(JSON.stringify({ embedding }), {
    headers: { "Content-Type": "application/json" },
  });
});

Search for similar content using cosine similarity:

-- Find the 10 most similar documents to a query
CREATE OR REPLACE FUNCTION search_documents(
  query_embedding VECTOR(1536),
  match_count INT DEFAULT 10,
  match_threshold FLOAT DEFAULT 0.5
)
RETURNS TABLE (
  id BIGINT,
  content TEXT,
  metadata JSONB,
  similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
  SELECT
    d.id,
    d.content,
    d.metadata,
    1 - (d.embedding <=> query_embedding) AS similarity
  FROM documents d
  WHERE 1 - (d.embedding <=> query_embedding) > match_threshold
  ORDER BY d.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

Use it:

-- Search for documents similar to a query
SELECT * FROM search_documents(
  (SELECT embedding FROM documents WHERE content LIKE '%machine learning%' LIMIT 1),
  10,
  0.5
);

Build a RAG application

Retrieval-augmented generation (RAG) combines semantic search with LLM generation:

Step 1: Store your knowledge base

-- Insert documents with embeddings
INSERT INTO documents (content, metadata, embedding)
VALUES (
  'PostgreSQL is a powerful relational database with support for JSON, full-text search, and extensions like pgvector.',
  '{"source": "docs", "topic": "database"}',
  generate_embedding('PostgreSQL is a powerful relational database with support for JSON, full-text search, and extensions like pgvector.')
);

Step 2: Search for relevant context

-- Find context for a user question
WITH context AS (
  SELECT content
  FROM search_documents(
    generate_embedding('How do I use Postgres for AI?'),
    5,
    0.6
  )
)
SELECT string_agg(content, '\n\n') AS context
FROM context;

Step 3: Generate a response

Use an Edge Function to combine search results with an LLM:

// supabase/functions/chat/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

serve(async (req) => {
  const { question } = await req.json();
  
  // 1. Generate embedding for the question
  const embedResponse = await fetch("http://host.docker.internal:11434/api/embeddings", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "nomic-embed-text",
      prompt: question,
    }),
  });
  const { embedding } = await embedResponse.json();
  
  // 2. Search for relevant context
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );
  
  const { data: context } = await supabase.rpc("search_documents", {
    query_embedding: embedding,
    match_count: 5,
    match_threshold: 0.6,
  });
  
  // 3. Generate response with context
  const contextText = context?.map((c: any) => c.content).join("\n\n") || "";
  
  const llmResponse = await fetch("http://host.docker.internal:11434/api/generate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "llama3.2",
      prompt: `Context:\n${contextText}\n\nQuestion: ${question}\n\nAnswer based on the context:`,
      stream: false,
    }),
  });
  
  const { response } = await llmResponse.json();
  
  return new Response(JSON.stringify({ answer: response, context }), {
    headers: { "Content-Type": "application/json" },
  });
});

Performance optimization

Index tuning: The IVFFlat index needs enough lists for your data size. General rule:

  • < 1M rows: 100 lists
  • 1M-10M rows: 1000 lists
  • 10M+ rows: 10000 lists

Rebuild the index after significant data changes:

REINDEX INDEX documents_embedding_idx;

Query optimization: Use EXPLAIN ANALYZE to check query plans:

EXPLAIN ANALYZE
SELECT * FROM search_documents(
  generate_embedding('test query'),
  10,
  0.5
);

Connection pooling: Supabase uses PgBouncer for connection pooling. For high-volume AI applications, configure the pool size appropriately.

Backups

Supabase backups include pgvector data automatically. For manual backups:

docker compose exec -T db pg_dump -U postgres supabase | gzip > supabase-ai-$(date +%F).sql.gz

The backup includes all tables, indexes, and extensions.

Troubleshooting

pgvector not found. Make sure the extension is enabled: CREATE EXTENSION IF NOT EXISTS vector;. If it fails, check that your Supabase version includes pgvector.

Embedding dimension mismatch. The vector column dimension must match your embedding model's output. Check your model's documentation for the correct dimension.

Slow similarity search. Check that you have an index on the embedding column. For large datasets, increase the number of lists in the IVFFlat index.

Out of memory during embedding generation. Generating embeddings for large documents can use significant memory. Process documents in batches rather than all at once.

Edge Function timeouts. LLM calls can be slow. Set appropriate timeouts and consider streaming responses for better user experience.

Verification + next steps

You're done when you can: store documents with embeddings, search by semantic similarity, and generate responses using retrieved context. Test with a small knowledge base, then scale up.

From here, explore advanced features like hybrid search (combining vector and full-text search), multi-tenant embeddings, and real-time embedding updates. For the base Supabase setup, see Self-Host Supabase. For document processing, see AnythingLLM. A 4 vCPU / 8 GB VPS handles small to medium datasets; for large-scale vector search, consider 8 vCPU / 16 GB for the extra RAM. See Best VPS for AI & ML Workloads for the ranked picks.

Next steps

How to self-host Supabase →More self-hosted backend & baas tools →Best VPS for Supabase →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.