Guide

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.

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

Next steps