Vector similarity search finds vectors most similar to a query vector using distance metrics. You can query vectors using the JavaScript SDK or directly from Postgres using SQL.
Basic similarity search
JavaScript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'your-service-key')
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')
// Query with a vector embedding
const { data, error } = await index.queryVectors({
queryVector: {
float32: [0.1, 0.2, 0.3 /* ... embedding of 1536 dimensions ... */],
},
topK: 5,
returnDistance: true,
returnMetadata: true,
})
if (error) {
console.error('Query failed:', error)
} else {
// Results are ranked by similarity (lowest distance = most similar)
data.vectors.forEach((result, rank) => {
console.log(`${rank + 1}. ${result.metadata?.title}`)
console.log(` Similarity score: ${result.distance.toFixed(4)}`)
})
}
SQL (via S3 Vector Wrapper)
-- Setup S3 Vector Wrapper (one-time setup)
-- https://docs.zuvodev.com/guides/database/extensions/wrappers/s3_vectors
-- Query similar vectors
SELECT
key,
metadata->>'title' as title,
embd_distance(data) as distance,
(1 - embd_distance(data)) as similarity_score
FROM s3_vectors.documents_openai
WHERE data <==> '[0.1, 0.2, 0.3, /* ... embedding ... */]'::embd
ORDER BY embd_distance(data) ASC
LIMIT 5;
Semantic search
Find documents similar to a query by embedding the query text:
JavaScript
import { createClient } from '@supabase/supabase-js'
import OpenAI from 'openai'
const supabase = createClient(...)
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
async function semanticSearch(query, topK = 5) {
// Embed the query
const queryEmbedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: query
})
const queryVector = queryEmbedding.data[0].embedding
// Search for similar vectors
const { data, error } = await supabase.storage.vectors
.from('embeddings')
.index('documents-openai')
.queryVectors({
queryVector: { float32: queryVector },
topK,
returnDistance: true,
returnMetadata: true
})
if (error) {
throw error
}
return data.vectors.map((result) => ({
id: result.key,
title: result.metadata?.title,
similarity: 1 - result.distance, // Convert distance to similarity (0-1)
metadata: result.metadata
}))
}
// Usage
const results = await semanticSearch('How do I use vector search?')
results.forEach((result) => {
console.log(`${result.title} (${(result.similarity * 100).toFixed(1)}% similar)`)
})
SQL (via S3 Vector Wrapper)
-- Semantic search from PostgreSQL
-- First, generate embedding from external API and store in a variable
-- Then use it to query vectors
WITH query_embedding AS (
SELECT '[/* ... embedding from OpenAI API ... */]'::embd as embedding
)
SELECT
key,
metadata->>'title' as title,
embd_distance(data) as distance,
(1 - embd_distance(data)) as similarity_score
FROM s3_vectors.documents_openai,
query_embedding
WHERE data <==> query_embedding.embedding
ORDER BY embd_distance(data) ASC
LIMIT 5;
Filtered similarity search
JavaScript
const index = supabase.storage.vectors
.from('embeddings')
.index('documents-openai')
// Search with metadata filter
const { data } = await index.queryVectors({
queryVector: { float32: [...embedding...] },
topK: 10,
filter: {
// Filter by metadata fields
category: 'electronics',
in_stock: true,
price: { $lte: 500 } // Less than or equal to 500
},
returnDistance: true,
returnMetadata: true
})
SQL (via S3 Vector Wrapper)
-- Search with metadata filters
SELECT
key,
metadata->>'title' as title,
(metadata->>'price')::numeric as price,
embd_distance(data) as distance,
(1 - embd_distance(data)) as similarity
FROM s3_vectors.documents_openai
WHERE data <==> '[...]'::embd
AND (metadata->>'category') = 'electronics'
AND (metadata->>'in_stock')::boolean = true
AND (metadata->>'price')::numeric <= 500
ORDER BY embd_distance(data) ASC
LIMIT 10;
Retrieving specific vectors
JavaScript
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')
const { data, error } = await index.getVectors({
keys: ['doc-1', 'doc-2', 'doc-3'],
returnData: true,
returnMetadata: true,
})
if (!error) {
data.vectors.forEach((vector) => {
console.log(`${vector.key}: ${vector.metadata?.title}`)
})
}
SQL (via S3 Vector Wrapper)
-- Retrieve specific vectors by key
select
key,
data as embedding,
metadata
from s3_vectors.documents_openai
where key in ('doc-1', 'doc-2', 'doc-3');
Listing vectors
JavaScript
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')
let nextToken = undefined
let pageCount = 0
do {
const { data, error } = await index.listVectors({
maxResults: 100,
nextToken,
returnData: false, // Don't return embeddings for faster response
returnMetadata: true,
})
if (error) break
pageCount++
console.log(`Page ${pageCount}: ${data.vectors.length} vectors`)
data.vectors.forEach((vector) => {
console.log(` - ${vector.key}: ${vector.metadata?.title}`)
})
nextToken = data.nextToken
} while (nextToken)
SQL (via S3 Vector Wrapper)
-- List all vectors with pagination
select
key,
metadata
from s3_vectors.documents_openai
order by key asc
limit 100 offset 0;
-- To paginate, increase OFFSET for next page:
-- OFFSET 100 for page 2, OFFSET 200 for page 3, etc.
Hybrid search: Vectors + relational data
Combine similarity search with SQL filtering and joins:
JavaScript
async function hybridSearch(queryVector, filters) {
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')
// Get similar vectors with filters
const { data: vectorResults } = await index.queryVectors({
queryVector: { float32: queryVector },
topK: 100,
filter: filters,
returnDistance: true,
returnMetadata: true,
})
// Get additional details from relational database
const { data: details } = await supabase
.from('documents')
.select('*')
.in(
'id',
vectorResults.vectors.map((v) => v.metadata?.doc_id)
)
// Merge results
return vectorResults.vectors.map((vector) => {
const detail = details?.find((d) => d.id === vector.metadata?.doc_id)
return {
...vector,
...detail,
}
})
}
SQL (via S3 Vector Wrapper)
-- Hybrid search: vectors + relational data join
SELECT
v.key as vector_id,
v.metadata->>'title' as vector_title,
d.id as document_id,
d.full_text,
d.author,
d.created_at,
embd_distance(v.data) as similarity_score
FROM s3_vectors.documents_openai v
LEFT JOIN public.documents d
ON v.metadata->>'doc_id' = d.id::text
WHERE v.data <==> '[...]'::embd
AND embd_distance(v.data) < 0.3 -- High similarity threshold
AND d.category = 'articles'
ORDER BY embd_distance(v.data) ASC
LIMIT 50;
Real-world examples
RAG (retrieval-augmented generation)
JavaScript
import OpenAI from 'openai'
import { createClient } from '@supabase/supabase-js'
async function retrieveContextForLLM(userQuery) {
const supabase = createClient(...)
const openai = new OpenAI()
// 1. Embed the user query
const queryEmbedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: userQuery
})
// 2. Retrieve relevant documents
const { data: vectorResults } = await supabase.storage.vectors
.from('embeddings')
.index('documents-openai')
.queryVectors({
queryVector: { float32: queryEmbedding.data[0].embedding },
topK: 5,
returnMetadata: true
})
// 3. Use vectors to augment LLM prompt
const context = vectorResults.vectors
.map(v => v.metadata?.content || '')
.join('\n\n')
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{
role: 'system',
content: `Use the following context to answer the user's question:\n\n${context}`
},
{
role: 'user',
content: userQuery
}
]
})
return response.choices[0].message.content
}
Product recommendations
JavaScript
async function recommendProducts(userEmbedding, topK = 5) {
const supabase = createClient(...)
// Find similar products
const { data } = await supabase.storage.vectors
.from('embeddings')
.index('products-openai')
.queryVectors({
queryVector: { float32: userEmbedding },
topK,
filter: {
in_stock: true
},
returnMetadata: true
})
return data.vectors.map((result) => ({
id: result.metadata?.product_id,
name: result.metadata?.name,
price: result.metadata?.price,
similarity: 1 - result.distance
}))
}
Filtering before similarity search
JavaScript
// Use metadata filters to reduce search scope
const { data } = await index.queryVectors({
queryVector,
topK: 100,
filter: {
category: 'electronics', // Pre-filter by category
},
})