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>
<script>
async function ask() {
const query = document.getElementById("query").value;
const res = await fetch("https://your-project.supabase.co/functions/v1/rag-query", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ query }),
});
const { response } = await res.json();
document.getElementById("response").innerText = response;
}
</script>
</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.
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.