Skip to main content
pgvector adds vector similarity search capabilities to PostgreSQL, allowing you to store embeddings alongside your relational data.

Installation

npm install @mastra/pg

Prerequisites

Install the pgvector extension in your PostgreSQL database:
CREATE EXTENSION IF NOT EXISTS vector;
For managed PostgreSQL services:
  • Supabase: Enabled by default
  • AWS RDS: Install from AWS Extensions
  • Neon: Enabled by default
  • Railway: Requires PostgreSQL 14+

Configuration

1

Import PgVector

import { PgVector } from '@mastra/pg';
2

Create vector store instance

const vectorStore = new PgVector({
  id: 'embeddings',
  connectionString: process.env.DATABASE_URL!,
});
3

Configure Mastra

import { Mastra } from '@mastra/core';

const mastra = new Mastra({
  vectors: {
    embeddings: vectorStore,
  },
});

Configuration Options

id
string
required
Unique identifier for the vector store instance
connectionString
string
PostgreSQL connection string
host
string
Database host (alternative to connectionString)
port
number
default:5432
Database port
database
string
Database name
user
string
Database user
password
string
Database password
schemaName
string
default:"public"
PostgreSQL schema for vector tables
max
number
default:20
Maximum pool connections

Vector Operations

Create Index

const vectorStore = new PgVector({
  id: 'embeddings',
  connectionString: process.env.DATABASE_URL!,
});

// Create with HNSW index (recommended)
await vectorStore.createIndex({
  indexName: 'documents',
  dimension: 1536,
  metric: 'cosine',
  indexConfig: {
    type: 'hnsw',
    hnsw: {
      m: 16,
      efConstruction: 64,
    },
  },
});

Index Types

pgvector supports three index types: Best for most use cases:
await vectorStore.createIndex({
  indexName: 'documents',
  dimension: 1536,
  metric: 'cosine',
  indexConfig: {
    type: 'hnsw',
    hnsw: {
      m: 16, // Number of connections per layer
      efConstruction: 64, // Size of dynamic candidate list
    },
  },
});

IVFFlat

Good for large datasets where build time matters:
await vectorStore.createIndex({
  indexName: 'documents',
  dimension: 1536,
  metric: 'cosine',
  indexConfig: {
    type: 'ivfflat',
    ivf: {
      lists: 100, // Number of clusters
    },
  },
});
No index, always exact but slower:
await vectorStore.createIndex({
  indexName: 'documents',
  dimension: 1536,
  metric: 'cosine',
  indexConfig: {
    type: 'flat',
  },
});

Vector Types

pgvector 0.7.0+ supports halfvec for 2x memory savings:
// Full precision (default) - max 2000 dimensions for indexes
await vectorStore.createIndex({
  indexName: 'documents',
  dimension: 1536,
  vectorType: 'vector',
});

// Half precision - max 4000 dimensions for indexes
await vectorStore.createIndex({
  indexName: 'large_embeddings',
  dimension: 3072, // text-embedding-3-large
  vectorType: 'halfvec',
});

Upsert Vectors

const vectors = [
  [0.1, 0.2, 0.3, ...],
  [0.4, 0.5, 0.6, ...],
];

const metadata = [
  { text: 'First document', category: 'tech' },
  { text: 'Second document', category: 'business' },
];

const ids = await vectorStore.upsert({
  indexName: 'documents',
  vectors,
  metadata,
});

Query Similar Vectors

const queryVector = [0.15, 0.25, 0.35, ...];

const results = await vectorStore.query({
  indexName: 'documents',
  queryVector,
  topK: 5,
  includeVector: false,
});

results.forEach(result => {
  console.log(`Score: ${result.score}`);
  console.log(`Text: ${result.metadata.text}`);
});

Query with Metadata Filters

const results = await vectorStore.query({
  indexName: 'documents',
  queryVector,
  topK: 5,
  filter: {
    category: { $eq: 'tech' },
    year: { $gte: 2020 },
  },
});

HNSW Query Tuning

const results = await vectorStore.query({
  indexName: 'documents',
  queryVector,
  topK: 5,
  ef: 100, // Higher = better recall, slower search
});

Update Vector

await vectorStore.updateVector({
  indexName: 'documents',
  id: 'vector-id-123',
  update: {
    vector: [0.2, 0.3, 0.4, ...],
    metadata: { text: 'Updated' },
  },
});

Delete Operations

// Delete by ID
await vectorStore.deleteVector({
  indexName: 'documents',
  id: 'vector-id-123',
});

// Delete by filter
await vectorStore.deleteVectors({
  indexName: 'documents',
  filter: {
    category: { $eq: 'archived' },
  },
});

// Delete multiple by IDs
await vectorStore.deleteVectors({
  indexName: 'documents',
  ids: ['id1', 'id2', 'id3'],
});

Index Management

// List indexes
const indexes = await vectorStore.listIndexes();

// Describe index
const stats = await vectorStore.describeIndex({
  indexName: 'documents',
});
console.log('Type:', stats.type); // 'hnsw', 'ivfflat', or 'flat'
console.log('Vector Type:', stats.vectorType); // 'vector' or 'halfvec'
console.log('Dimension:', stats.dimension);
console.log('Count:', stats.count);

// Delete index
await vectorStore.deleteIndex({
  indexName: 'documents',
});

// Truncate (delete all vectors)
await vectorStore.truncateIndex({
  indexName: 'documents',
});

RAG Integration

import { Mastra } from '@mastra/core';
import { PgVector } from '@mastra/pg';
import { createOpenAI } from '@ai-sdk/openai';
import { embed } from 'ai';

const mastra = new Mastra({
  vectors: {
    embeddings: new PgVector({
      id: 'embeddings',
      connectionString: process.env.DATABASE_URL!,
    }),
  },
});

const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});

// Create index
await mastra.vectors.embeddings.createIndex({
  indexName: 'docs',
  dimension: 1536,
  indexConfig: {
    type: 'hnsw',
  },
});

// Index document
const { embedding } = await embed({
  model: openai.embedding('text-embedding-3-small'),
  value: 'Document text',
});

await mastra.vectors.embeddings.upsert({
  indexName: 'docs',
  vectors: [embedding],
  metadata: [{ text: 'Document text', source: 'manual' }],
});

// Query
const { embedding: queryEmbedding } = await embed({
  model: openai.embedding('text-embedding-3-small'),
  value: 'User question',
});

const results = await mastra.vectors.embeddings.query({
  indexName: 'docs',
  queryVector: queryEmbedding,
  topK: 3,
});

Distance Metrics

  • cosine - Cosine similarity (recommended)
  • euclidean - Euclidean distance (L2)
  • dotproduct - Inner product

Combined Storage and Vectors

import { PostgresStore, PgVector } from '@mastra/pg';
import { Mastra } from '@mastra/core';

const mastra = new Mastra({
  storage: new PostgresStore({
    id: 'storage',
    connectionString: process.env.DATABASE_URL!,
  }),
  vectors: {
    embeddings: new PgVector({
      id: 'vectors',
      connectionString: process.env.DATABASE_URL!,
    }),
  },
});

Best Practices

Use HNSW for Most Cases

HNSW provides the best balance of speed and accuracy.

halfvec for Large Embeddings

Use vectorType: 'halfvec' for embeddings > 2000 dimensions.

Tune ef_search

Increase ef parameter at query time for better recall.

Batch Upserts

Insert vectors in batches of 100-1000 for performance.

Performance Tuning

HNSW Parameters

  • m: 16 (default) - Higher = better recall, larger index
  • efConstruction: 64 (default) - Higher = better quality, slower build
  • Query ef: 100+ - Higher = better recall, slower queries

IVFFlat Parameters

  • lists: sqrt(rows) * 2 - Number of clusters
  • probes: 10 - Number of clusters to search

PostgreSQL Storage

PostgreSQL storage adapter

Pinecone

Managed vector database alternative

pgvector Docs

Official pgvector documentation

Supabase Vector

pgvector on Supabase

Build docs developers (and LLMs) love