ANTIGRAVITY LABJP
Articles/Integrations
Integrations/2026-05-05Intermediate

Building a RAG App with Supabase Vector (pgvector) and Antigravity

Learn how to build a Retrieval-Augmented Generation (RAG) app using Supabase's pgvector extension and Antigravity. Covers embedding generation, similarity search, and Gemini API integration with practical code examples.

supabase7pgvector2rag8antigravity429vector-search3gemini16

"I want to search our internal docs with a chatbot" is one of the most common requests I hear from fellow indie developers. When that request comes up, my go-to stack is Supabase Vector + Gemini API — and Antigravity makes the whole thing surprisingly fast to build.

Since pgvector is a PostgreSQL extension, you can bolt vector storage directly onto your existing Supabase project. No extra services, no extra API keys. Combine that with Antigravity's ability to generate schema, SQL, and embedding logic on demand, and a working RAG prototype is a realistic weekend project.

This guide walks through a complete Supabase pgvector RAG implementation, using Antigravity as your coding partner throughout.

Why Choose Supabase Vector (pgvector)?

The RAG ecosystem offers plenty of vector store options — Pinecone, Weaviate, Qdrant, and more. The case for Supabase pgvector comes down to zero additional infrastructure: if you're already running Supabase, you enable the extension and add a vector column to any table. That's it.

Beyond simplicity, pgvector plays nicely with Supabase's Row Level Security. Multi-tenant RAG — where each user can only query their own documents — is straightforward with existing RLS policies, no custom middleware required.

One honest caveat: pgvector's performance at very large scales (tens of millions of vectors) won't match purpose-built vector databases. For most indie projects and internal tools, though, it's more than sufficient.

Setting Up the Environment with Antigravity

Open Antigravity's chat panel and describe your goal: "I want to build a RAG app using Supabase pgvector, TypeScript, Next.js 15 App Router, and Supabase JS v2. List the packages and setup steps."

The generated bootstrap should look roughly like this:

# Add dependencies to your Next.js project
npm install @supabase/supabase-js @ai-sdk/google ai
 
# .env.local
# NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# GOOGLE_GENERATIVE_AI_API_KEY=YOUR_GEMINI_API_KEY

Then head to the Supabase SQL Editor to enable pgvector and create your documents table:

-- Enable pgvector extension
create extension if not exists vector;
 
-- Documents table (768 dimensions = text-embedding-004 output size)
create table documents (
  id uuid default gen_random_uuid() primary key,
  content text not null,
  metadata jsonb default '{}',
  embedding vector(768),
  created_at timestamptz default now()
);
 
-- IVFFlat index for cosine similarity search
create index on documents
  using ivfflat (embedding vector_cosine_ops)
  with (lists = 100);

Paste this schema into Antigravity's context and ask it to generate TypeScript types and a typed Supabase client. Having the schema in context keeps subsequent code generation accurate.

Vectorizing Documents and Saving to Supabase

The heart of any RAG system is converting documents into embedding vectors and storing them. Ask Antigravity: "Write a function that embeds text using Gemini's text-embedding-004 model and upserts it to Supabase."

// lib/embeddings.ts
import { createClient } from "@supabase/supabase-js";
import { GoogleGenerativeAI } from "@google/generative-ai";
 
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
 
const genai = new GoogleGenerativeAI(process.env.GOOGLE_GENERATIVE_AI_API_KEY!);
const embeddingModel = genai.getGenerativeModel({ model: "text-embedding-004" });
 
// Convert text to a 768-dimensional vector and save to Supabase
export async function upsertDocument(
  content: string,
  metadata: Record<string, unknown> = {}
): Promise<{ id: string }> {
  const result = await embeddingModel.embedContent(content);
  const embedding = result.embedding.values; // 768 float values
 
  const { data, error } = await supabase
    .from("documents")
    .insert({ content, metadata, embedding })
    .select("id")
    .single();
 
  if (error) throw new Error(`Supabase insert error: ${error.message}`);
  return { id: data.id };
}

The critical detail: text-embedding-004 outputs 768 dimensions. Your table's vector(768) column must match. Switching models later means a schema migration. Worth knowing upfront.

Semantic Search and Gemini Response Generation

Once documents are stored, you need two things: a semantic search function and a generation step that feeds retrieved chunks to Gemini. Antigravity can generate both together when you provide the schema context.

First, register a PostgreSQL RPC function in Supabase:

create or replace function match_documents (
  query_embedding vector(768),
  match_threshold float,
  match_count int
)
returns table (
  id uuid,
  content text,
  metadata jsonb,
  similarity float
)
language sql stable
as $$
  select
    id,
    content,
    metadata,
    1 - (embedding <=> query_embedding) as similarity
  from documents
  where 1 - (embedding <=> query_embedding) > match_threshold
  order by similarity desc
  limit match_count;
$$;

The <=> operator is pgvector's cosine distance. Subtracting from 1 converts it to a similarity score (0–1, higher is more similar).

Now wire it into a RAG query function:

// lib/rag.ts
import { generateText } from "ai";
import { google } from "@ai-sdk/google";
 
async function searchDocuments(query: string, topK = 5) {
  const result = await embeddingModel.embedContent(query);
  const queryEmbedding = result.embedding.values;
 
  const { data, error } = await supabase.rpc("match_documents", {
    query_embedding: queryEmbedding,
    match_threshold: 0.7,
    match_count: topK,
  });
 
  if (error) throw new Error(`Search error: ${error.message}`);
  return data as Array<{ content: string; metadata: unknown; similarity: number }>;
}
 
export async function ragQuery(userQuestion: string): Promise<string> {
  const relevantDocs = await searchDocuments(userQuestion);
 
  if (relevantDocs.length === 0) {
    return "No relevant documents found.";
  }
 
  const context = relevantDocs
    .map((doc, i) => `[Document ${i + 1}]\n${doc.content}`)
    .join("\n\n");
 
  const { text } = await generateText({
    model: google("gemini-2.0-flash"),
    prompt: `Answer the following question based on the provided documents.\n\n${context}\n\nQuestion: ${userQuestion}`,
  });
 
  return text;
}

Common Pitfalls and How to Fix Them

A few issues come up almost every time with this stack, so it's worth calling them out explicitly.

Dimension mismatch: If you change embedding models, you must update the vector column dimensions too. A vector(768) column will reject 1,536-dimensional OpenAI embeddings with a runtime error. The fix is an ALTER TABLE migration or a fresh table.

IVFFlat index timing: Creating the index before you have enough data (the recommendation is roughly sqrt(row_count) for the lists parameter) gives you little performance benefit while slowing inserts. Add the index once you have at least 1,000 rows.

RLS bypassing with service role key: If you call RPCs from a server-side route using the service_role key, RLS policies are bypassed. For user-scoped document access, use the anon key with an active user session so RLS can enforce row filtering correctly.

Antigravity handles error diagnosis well — paste the actual error message into the chat and it'll identify the cause and generate a fix. For a broader look at what you can build with Supabase and Antigravity together, check out the Supabase integration guide.

Next Steps

Supabase pgvector gives you a low-friction entry point into production RAG — no new services to manage, no additional billing to track. Start by ingesting 10–20 documents and testing search quality with real queries before scaling up.

Once you're happy with retrieval accuracy, the next meaningful improvement is chunk size and overlap tuning. The RAG pipeline vector search guide covers those design decisions in depth.

Swapping generateText for streamText from the Vercel AI SDK adds streaming responses to your chat UI with minimal code changes — a small upgrade that makes a big difference in perceived responsiveness.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Integrations2026-07-06
Semantic Search Over Your Own Codebase with Gemini Embeddings and sqlite-vec: An Incremental Index Keyed on Git Blob Hashes
A build log for semantic search scoped to your own repository, using Gemini embeddings and sqlite-vec. Covers an incremental pipeline that skips re-embedding unchanged files via git blob hashes, with measured index size and query latency.
Integrations2026-04-17
Google Antigravity Python SDK Production Masterguide: Multimodal, Agents, and RAG Pipelines from Design to Deployment
The complete guide to using the Google Antigravity Python SDK in production. Covers multimodal input, tool calling, RAG pipelines, streaming, cost optimization, and Cloud Run deployment with working code examples.
Integrations2026-04-12
Antigravity × Gemini API Multimodal Complete Implementation Guide: Building Production-Ready AI Apps with Text, Images, and Audio
A comprehensive guide to building production-grade multimodal AI apps using Antigravity and the Gemini API. Covers text, image, audio, and document processing with real code, cost optimization strategies, and robust error handling patterns.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →