Back to blog
SupabaseAIVector SearchTutorial

Building AI Apps with Supabase: Vector Search and Beyond

How to use Supabase as your vector database for AI applications, with pgvector, embeddings, and semantic search.

3 min read

The Supabase + AI Stack

Supabase has quietly become a strong platform for AI applications. With native pgvector support, you get a production-ready vector database alongside your relational data. No extra infrastructure.

Why Supabase for AI?

  • pgvector built in - no separate vector DB needed
  • Row Level Security - secure your embeddings like any other data
  • Edge Functions - run inference at the edge
  • Realtime - stream AI responses to connected clients

Setting Up pgvector

Enable the vector extension in your Supabase project:

-- Enable the vector extension
create extension if not exists vector;
 
-- Create a documents table with embeddings
create table documents (
  id bigserial primary key,
  content text not null,
  embedding vector(1536),
  metadata jsonb default '{}'::jsonb,
  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);

Generating Embeddings

Use Azure OpenAI to generate embeddings for your content:

import { AzureOpenAI } from "openai";
import { createClient } from "@supabase/supabase-js";
 
const openai = new AzureOpenAI({
  endpoint: process.env.AZURE_OPENAI_ENDPOINT!,
  apiKey: process.env.AZURE_OPENAI_KEY!,
  apiVersion: "2024-10-21",
});
 
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);
 
async function embedAndStore(content: string) {
  // Generate embedding
  const response = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: content,
  });
 
  const embedding = response.data[0].embedding;
 
  // Store in Supabase
  const { error } = await supabase.from("documents").insert({
    content,
    embedding,
  });
 
  if (error) throw error;
}

Now query your documents using cosine similarity:

async function search(query: string, limit = 5) {
  // Embed the query
  const response = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: query,
  });
 
  const embedding = response.data[0].embedding;
 
  // Search by similarity
  const { data, error } = await supabase.rpc("match_documents", {
    query_embedding: embedding,
    match_threshold: 0.78,
    match_count: limit,
  });
 
  if (error) throw error;
  return data;
}

Create the matching function in SQL:

create or replace function match_documents(
  query_embedding vector(1536),
  match_threshold float,
  match_count int
)
returns table (
  id bigint,
  content text,
  similarity float
)
language sql stable
as $$
  select
    documents.id,
    documents.content,
    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;
$$;

Production Tips

  1. Use text-embedding-3-small - It's 5x cheaper than ada-002 with better performance
  2. Batch your embeddings - The API supports up to 2048 inputs per request
  3. Index wisely - IVFFlat is great for < 1M vectors; switch to HNSW for larger datasets
  4. Cache embeddings - Store query embeddings to avoid re-computing for repeated searches

What's Next

In the next post, we'll combine this vector search with Azure OpenAI to build a full RAG (Retrieval-Augmented Generation) pipeline - complete with streaming responses and a polished UI.