Excellent hybrid search in the Postgres you own.
searchgres is an open-source TypeScript library that combines BM25 keyword
search, vector search, Reciprocal Rank Fusion, and structured filters in
PostgreSQL. Bring a postgres.js
connection and an AI SDK embedding model; searchgres
manages the search schema, indexes, and embedding workflow.
- Find both exact terms and related meaning.
- Scope retrieval by hierarchy, JSON metadata, time, and regex.
- Keep your search index in PostgreSQL you control, not a separate vector service.
- Use the library directly without adopting a RAG framework, server, or content model.
npm install searchgres postgres @ai-sdk/openaiimport { openai } from "@ai-sdk/openai";
import postgres from "postgres";
import { createIndex, openIndex } from "searchgres";
const sql = postgres(process.env.DATABASE_URL);
// Run once. An index is a Postgres schema managed by searchgres.
await createIndex(sql, "docs_index", { dimensions: 1536 });
const index = await openIndex(sql, "docs_index", {
embedding: openai.embedding("text-embedding-3-small"),
});
await index.upsertMany([
{
content: "Auth tokens rotate every 24 hours.",
tree: "docs.auth",
meta: { audience: "operators" },
},
{
content: "Rate limits are 100 requests per minute for each API key.",
tree: "docs.api",
meta: { audience: "developers" },
},
]);
// New records already work with BM25 and filters. Drain before semantic search.
await index.processEmbeddings();
const hits = await index.search({
semantic: "how are request limits enforced?",
fulltext: "rate limit",
filter: {
and: [
{ tree: "docs.api" },
{ meta: { audience: "developers" } },
],
},
limit: 5,
});
for (const hit of hits) console.log(hit.score, hit.tree, hit.content);
await sql.end(); // you own the poolSee Get started for the complete walkthrough.
Pure semantic search struggles with identifiers, exact phrases, dates, and scope. searchgres gives each query the retrieval strategy it needs:
- BM25 for exact and lexical relevance.
- Vector search for related meaning when wording differs.
- RRF hybrid search to combine both rankings without mixing incompatible raw score scales.
- Composable filters over tree paths, JSONB metadata and JSONPath, temporal ranges, and regex.
- Filter-only listing when ranking is unnecessary.
A searchgres index is an ordinary PostgreSQL schema containing records, SQL
routines, and native indexes: HNSW through pgvector, BM25 through
pg_textsearch, GiST for hierarchy and time, and GIN for metadata.
That means one database to operate, one transaction system, normal backups and replication, and direct SQL access when you need it. Your search index lives in PostgreSQL you control rather than in a separate proprietary service.
searchgres owns the mechanics of retrieval, not your application architecture:
- You own the connection pool, embedding provider, credentials, and source data.
- You decide how to chunk, summarize, extract, or otherwise derive records.
- You can organize raw and derived records in one index or several indexes.
- You can call the TypeScript API or the schema-local SQL routines.
- The core contains no user, account, or authorization model.
No fact-extraction pipeline, opaque summarization, or RAG framework is imposed.
index.search() infers the retrieval mode from the arguments you pass:
| Input | Behavior |
|---|---|
semantic or a precomputed vector |
HNSW cosine search |
fulltext |
BM25 keyword search |
semantic and fulltext |
RRF hybrid search |
| filters only | UUIDv7-ordered listing with keyset pagination |
either ranked arg plus filter |
Filtered and ranked retrieval |
both ranked args plus filter |
Filtered and RRF hybrid search retrieval |
Every hit is the full record plus its score. Newly written records are available
to BM25 and filters immediately; they join semantic and hybrid results after the
built-in embedding queue is drained. Run a bounded processEmbeddings() pass or
start a concurrency-safe background EmbeddingWorker.
Read How search works or jump to Search and filter.
The core is deliberately a library. You can use it to build:
- application search or a hosted search API;
- an indexing pipeline fed from existing tables through code, SQL, or triggers;
- RAG retrieval with your own chunking and generation stages;
- raw, summarized, or fact-extracted representations organized by tree;
- application-enforced access scopes by injecting mandatory tree or metadata filters;
- a post-retrieval reranking stage.
Authentication and authorization remain an application or database concern. Filters become an access-control boundary only when callers cannot bypass the layer that injects them or issue unrestricted database queries.
See Architecture and responsibilities and Choosing searchgres.
The architecture behind searchgres was evaluated on conversational-memory and multi-hop retrieval benchmarks using a simplified, prototype based on the same core approach: one Postgres record table, BM25, HNSW vectors, RRF, and structured filters—without knowledge graphs or fact-extraction pipelines.
- LoCoMo:
F1=0.666after search-tool refinement, compared withF1=0.493for the fixed retrieval baseline in the same experiments. - MuSiQue:
86.5%retrieval recall on a seeded 100-question sample spanning two-, three-, and four-hop questions.
These are architecture experiments, not a current leaderboard claim; answering models, agent behavior, samples, and metrics also affect end-to-end scores.
- PostgreSQL 18 with these extensions installed in
public:pgvector,pg_textsearch, andltree.pg_textsearchmust be included inshared_preload_libraries. - Node ≥ 22, Bun ≥ 1.4, or Deno ≥ 2.0.
createIndex() installs missing extensions when its database role has the
necessary privileges. The repository includes a PostgreSQL Dockerfile with the
extensions configured. See Install searchgres.
The core library is the primary product. This repository also includes optional applications built on it:
searchgres-serverexposes one configured index over HTTP.searchgresis a remote CLI for records, trees, import/export, and search.searchgres-mcpexposes searchgres tools to MCP-compatible agents.- The Docker Compose stack runs PostgreSQL, Ollama, provisioning, and the server for a no-API-key local evaluation.
Use them as reference implementations, an evaluation environment, or as-is for remote and agentic search. Start the local evaluation with:
git clone https://github.com/timescale/searchgres.git
cd searchgres
docker compose up --buildSee the Docker Compose evaluation guide or API server guide.
- Get started
- How search works
- Model records
- Architecture and responsibilities
- Create and manage indexes
- Ingest records
- Generate embeddings
- Search and filter
- Build a RAG retriever
- Manage records and trees
- Run in production
- Runnable examples
- Choosing searchgres
- Configure the API server
- Evaluate with Docker Compose
- Use the MCP server
searchgres is derived from the search engine core of Memory Engine. See the NOTICE.