Skip to content
Storage

Querying Vectors

Perform similarity search and retrieve vectors using JavaScript SDK or Postgres.

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.

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)}`)
})
}

Find documents similar to a query by embedding the query text:

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)`)
})
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
})

Retrieving specific vectors#

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}`)
})
}

Listing vectors#

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)

Hybrid search: Vectors + relational data#

Combine similarity search with SQL filtering and joins:

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,
}
})
}

Real-world examples#

RAG (retrieval-augmented generation)#

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#

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
}))
}
// Use metadata filters to reduce search scope
const { data } = await index.queryVectors({
queryVector,
topK: 100,
filter: {
category: 'electronics', // Pre-filter by category
},
})

Next steps#