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