Why Claude + Supabase for RAG?
Retrieval-Augmented Generation (RAG) combines vector search with LLMs like Claude to deliver accurate, context-rich responses. Supabase's pgvector extension handles embeddings at scale, while edge functions provide serverless inference. Pair this with Claude's superior reasoning (e.g., Claude 3.5 Sonnet), and you get low-latency, cost-effective RAG apps.
This guide walks you through building a full RAG pipeline:
- Ingest documents into a vector store
- Query with semantic search
- Generate responses via Claude API
Perfect for developers building AI agents, chatbots, or knowledge bases.
Prerequisites
Before diving in:
- Supabase account (free tier works)
- Anthropic API key (Claude access)
- OpenAI API key (for embeddings; Claude lacks native embeddings)
- Deno installed locally (for testing edge functions)
- Basic SQL and JavaScript knowledge
Pro Tip: Use Claude 3.5 Sonnet (claude-3-5-sonnet-20241022) for best RAG performance—its 200K context window handles large retrieved chunks effortlessly.
Step 1: Create a Supabase Project and Enable pgvector
- Log in to Supabase Dashboard and create a new project.
- In the SQL Editor, run:
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Create documents table
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB,
embedding VECTOR(1536) -- OpenAI text-embedding-3-small dimension
);
-- Index for fast vector search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
- Set up Row Level Security (RLS) for production:
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public read" ON documents FOR SELECT USING (true);
This sets up your vector database. pgvector's cosine similarity ensures semantic matches.
Step 2: Ingest Documents with Embeddings
We'll create an edge function to chunk, embed, and store docs. Edge functions run on Deno for global low-latency.
- In Supabase Dashboard > Edge Functions, create
upsert-docs:
// supabase/functions/upsert-docs/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";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
);
const { text, metadata } = await req.json();
const openaiApiKey = Deno.env.get("OPENAI_API_KEY")!;
// Chunk text (simple split for demo)
const chunks = text.match(/[^.\
]{1,800}/g) || [];
for (const chunk of chunks) {
// Embed with OpenAI
const embedRes = await fetch("https://api.openai.com/v1/embeddings", {
method: "POST",
headers: {
"Authorization": `Bearer ${openaiApiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "text-embedding-3-small",
input: chunk,
}),
});
const { data: [{ embedding }] } = await embedRes.json();
// Upsert to Supabase
await supabase.from("documents").insert({
content: chunk,
metadata,
embedding,
});
}
return new Response(JSON.stringify({ success: true }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
});
- Deploy:
supabase functions deploy upsert-docs - Set env vars in Dashboard:
OPENAI_API_KEY - Test: POST to
/functions/v1/upsert-docswith{ "text": "Your doc content", "metadata": {} }
Step 3: Build the RAG Query Edge Function
Now, the core: query → embed → retrieve → Claude.
Create rag-query function:
// supabase/functions/rag-query/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";
const corsHeaders = { /* same as above */ };
serve(async (req) => {
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
const supabase = createClient(/* same as above */);
const { query } = await req.json();
const openaiApiKey = Deno.env.get("OPENAI_API_KEY")!;
const anthropicApiKey = Deno.env.get("ANTHROPIC_API_KEY")!;
// 1. Embed query
const embedRes = await fetch("https://api.openai.com/v1/embeddings", {
// same as above, input: query
});
const { data: [{ embedding: queryEmbedding }] } = await embedRes.json();
// 2. Vector search (top 5)
const { data: docs } = await supabase.rpc("match_documents", {
query_embedding: queryEmbedding,
match_threshold: 0.78,
match_count: 5,
});
const context = docs.map(d => d.content).join("\
\
");
// 3. Call Claude API
const claudeRes = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": anthropicApiKey,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages: [{
role: "user",
content: `Use this context to answer: ${context}\
\
Question: ${query}`,
}],
}),
});
const { content: [{ text: response }] } = await claudeRes.json();
return new Response(JSON.stringify({ response, sources: docs }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
});
Key RPC Function: Add this SQL for similarity search:
CREATE OR REPLACE FUNCTION match_documents(
query_embedding VECTOR(1536),
match_threshold FLOAT,
match_count INT
)
RETURNS TABLE (
id BIGINT, content TEXT, metadata JSONB, similarity FLOAT
)
LANGUAGE SQL STABLE
AS $$
SELECT
documents.id,
documents.content,
documents.metadata,
1 - (documents.embedding <=> query_embedding) AS similarity
FROM documents
WHERE 1 - (documents.embedding <=> query_embedding) > match_threshold
ORDER BY documents.embedding <=> query_embedding
LIMIT match_count;
$$;
Deploy: supabase functions deploy rag-query (add ANTHROPIC_API_KEY env).
Step 4: Test Your RAG Pipeline
- Upsert sample docs:
curl -X POST https://your-project.supabase.co/functions/v1/upsert-docs \
-H "Authorization: Bearer YOUR_SERVICE_ROLE" \
-H "Content-Type: application/json" \
-d '{"text": "Claude 3.5 Sonnet excels in coding. It beats GPT-4o on benchmarks.", "metadata": {"source": "anthropic.com"}}'
- Query:
curl -X POST https://your-project.supabase.co/functions/v1/rag-query \
-H "Content-Type: application/json" \
-d '{"query": "What is Claude good at?"}'
Expect: Response citing your doc, with similarity scores.
Step 5: Add a Simple Frontend
For demo, create an HTML page:
<!DOCTYPE html>
<html>
<head><title>Claude RAG</title></head>
<body>
<input id="query" placeholder="Ask about your docs...">
<button onclick="ask()">Query</button>
<div id="response"></div>
</body>
</html>
Host on Vercel/Netlify. Boom—interactive RAG app!
Step 6: Optimize for Scale
- Chunking: Use recursive splitting for better granularity.
- Hybrid Search: Combine with full-text search:
-- Add to match_documents AND content ILIKE '%' || query_text || '%' - Rate Limits: Claude: 50 RPM (Sonnet). Use queues for bursts (e.g., Upstash Redis via Supabase).
- Costs: Embeddings ~$0.02/1M tokens, Claude ~$3/1M input.
Step 7: Secure Your App
- Use anon/public keys for client, service_role for functions.
- RLS policies: Limit inserts to auth users.
- Env secrets: Never hardcode API keys.
Step 8: Integrate with AI Agents
Extend to agents: Use Claude's tool-use for dynamic retrieval.
Prompt example:
{
"role": "user",
"content": [
{"type": "text", "text": "Answer using tools if needed."},
{
"type": "tool",
"tool_use_id": "toolu_123",
"name": "rag_search",
"input": {"query": "{{query}}"}
}
]
}
Call your edge function as a tool.
Step 9: Monitor and Debug
- Supabase Logs: Dashboard > Edge Functions > Logs.
- Claude Console: Track usage.
- Add tracing: OpenTelemetry in Deno.
Step 10: Deploy to Production
- Custom domain for edge functions.
- Auto-scaling: Supabase handles it.
- CI/CD: GitHub Actions with
supabase functions deploy. - Multi-region: Edge runtime is global.
Next Level: Integrate with n8n/Zapier for workflows or Claude Code for local dev.
Your RAG app is live! Scale to millions of vectors with Supabase's Postgres. Questions? Drop in comments.
(~1450 words)
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.