Skip to main content

PostgreSQL Storage

The PostgreSQL storage implementation provides a production-ready storage solution using PostgreSQL databases.

Installation

npm install @mastra/pg@latest

Usage

import { PostgresStore } from "@mastra/pg";

const storage = new PostgresStore({
connectionString: process.env.DATABASE_URL,
});

Parameters

connectionString:

string
PostgreSQL connection string (e.g., postgresql://user:pass@host:5432/dbname)

schemaName?:

string
The name of the schema you want the storage to use. Will use the default schema if not provided.

Constructor Examples

You can instantiate PostgresStore in the following ways:

import { PostgresStore } from "@mastra/pg";

// Using a connection string only
const store1 = new PostgresStore({
connectionString: "postgresql://user:password@localhost:5432/mydb",
});

// Using a connection string with a custom schema name
const store2 = new PostgresStore({
connectionString: "postgresql://user:password@localhost:5432/mydb",
schemaName: "custom_schema", // optional
});

// Using individual connection parameters
const store4 = new PostgresStore({
host: "localhost",
port: 5432,
database: "mydb",
user: "user",
password: "password",
});

// Individual parameters with schemaName
const store5 = new PostgresStore({
host: "localhost",
port: 5432,
database: "mydb",
user: "user",
password: "password",
schemaName: "custom_schema", // optional
});

Additional Notes

Schema Management

The storage implementation handles schema creation and updates automatically. It creates the following tables:

  • mastra_workflow_snapshot: Stores workflow state and execution data
  • mastra_evals: Stores evaluation results and metadata
  • mastra_threads: Stores conversation threads
  • mastra_messages: Stores individual messages
  • mastra_traces: Stores telemetry and tracing data
  • mastra_scorers: Stores scoring and evaluation data
  • mastra_resources: Stores resource working memory data

Direct Database and Pool Access

PostgresStore exposes both the underlying database object and the pg-promise instance as public fields:

store.db; // pg-promise database instance
store.pgp; // pg-promise main instance

This enables direct queries and custom transaction management. When using these fields:

  • You are responsible for proper connection and transaction handling.
  • Closing the store (store.close()) will destroy the associated connection pool.
  • Direct access bypasses any additional logic or validation provided by PostgresStore methods.

This approach is intended for advanced scenarios where low-level access is required.

Index Management

PostgreSQL storage provides comprehensive index management capabilities to optimize query performance.

Automatic Performance Indexes

PostgreSQL storage automatically creates composite indexes during initialization for common query patterns:

  • mastra_threads_resourceid_createdat_idx: (resourceId, createdAt DESC)
  • mastra_messages_thread_id_createdat_idx: (thread_id, createdAt DESC)
  • mastra_traces_name_starttime_idx: (name, startTime DESC)
  • mastra_evals_agent_name_created_at_idx: (agent_name, created_at DESC)

These indexes significantly improve performance for filtered queries with sorting.

Creating Custom Indexes

Create additional indexes to optimize specific query patterns:

// Basic index for common queries
await storage.createIndex({
name: "idx_threads_resource",
table: "mastra_threads",
columns: ["resourceId"],
});

// Composite index with sort order for filtering + sorting
await storage.createIndex({
name: "idx_messages_composite",
table: "mastra_messages",
columns: ["thread_id", "createdAt DESC"],
});

// GIN index for JSONB columns (fast JSON queries)
await storage.createIndex({
name: "idx_traces_attributes",
table: "mastra_traces",
columns: ["attributes"],
method: "gin",
});

For more advanced use cases, you can also use:

  • unique: true for unique constraints
  • where: 'condition' for partial indexes
  • method: 'brin' for time-series data
  • storage: { fillfactor: 90 } for update-heavy tables
  • concurrent: true for non-blocking creation (default)

Index Options

name:

string
Unique name for the index

table:

string
Table name (e.g., 'mastra_threads')

columns:

string[]
Array of column names with optional sort order (e.g., ['id', 'createdAt DESC'])

unique?:

boolean
Creates a unique constraint index

concurrent?:

boolean
Creates index without locking table (default: true)

where?:

string
Partial index condition (PostgreSQL specific)

method?:

'btree' | 'hash' | 'gin' | 'gist' | 'spgist' | 'brin'
Index method (default: 'btree')

opclass?:

string
Operator class for GIN/GIST indexes

storage?:

Record<string, any>
Storage parameters (e.g., { fillfactor: 90 })

tablespace?:

string
Tablespace name for index placement

Managing Indexes

List and monitor existing indexes:

// List all indexes
const allIndexes = await storage.listIndexes();
console.log(allIndexes);
// [
// {
// name: 'mastra_threads_pkey',
// table: 'mastra_threads',
// columns: ['id'],
// unique: true,
// size: '16 KB',
// definition: 'CREATE UNIQUE INDEX...'
// },
// ...
// ]

// List indexes for specific table
const threadIndexes = await storage.listIndexes("mastra_threads");

// Get detailed statistics for an index
const stats = await storage.describeIndex("idx_threads_resource");
console.log(stats);
// {
// name: 'idx_threads_resource',
// table: 'mastra_threads',
// columns: ['resourceId', 'createdAt'],
// unique: false,
// size: '128 KB',
// definition: 'CREATE INDEX idx_threads_resource...',
// method: 'btree',
// scans: 1542, // Number of index scans
// tuples_read: 45230, // Tuples read via index
// tuples_fetched: 12050 // Tuples fetched via index
// }

// Drop an index
await storage.dropIndex("idx_threads_status");

Schema-Specific Indexes

When using custom schemas, indexes are created with schema prefixes:

const storage = new PostgresStore({
connectionString: process.env.DATABASE_URL,
schemaName: "custom_schema",
});

// Creates index as: custom_schema_idx_threads_status
await storage.createIndex({
name: "idx_threads_status",
table: "mastra_threads",
columns: ["status"],
});

Index Types and Use Cases

PostgreSQL offers different index types optimized for specific scenarios:

Index TypeBest ForStorageSpeed
btree (default)Range queries, sorting, general purposeModerateFast
hashEquality comparisons onlySmallVery fast for =
ginJSONB, arrays, full-text searchLargeFast for contains
gistGeometric data, full-text searchModerateFast for nearest-neighbor
spgistNon-balanced data, text patternsSmallFast for specific patterns
brinLarge tables with natural orderingVery smallFast for ranges