MongoDB vector search.

Atlas vector index

db.docs.createSearchIndex({
    name: "vector_index",
    type: "vectorSearch",
    definition: {
        fields: [
            {
                type: "vector",
                path: "embedding",
                numDimensions: 1536,
                similarity: "cosine",        // or "dotProduct" or "euclidean"
            },
            {
                type: "filter",
                path: "category",
            },
        ],
    },
})

Insert with embedding

doc = {
    "text": "...",
    "category": "blog",
    "embedding": embed(text),    # list[float] of len 1536
}
db.docs.insert_one(doc)

Query

db.docs.aggregate([
    {
        $vectorSearch: {
            index: "vector_index",
            path: "embedding",
            queryVector: q_vec,
            numCandidates: 100,
            limit: 5,
            filter: { category: "blog" },
        },
    },
    {
        $project: {
            text: 1,
            score: { $meta: "vectorSearchScore" },
        },
    },
])

numCandidates

How many candidates to consider before returning limit. Higher → better recall, slower.

Default rule: numCandidates >= 10 * limit.

Hybrid (vector + keyword)

db.docs.aggregate([
    {
        $search: {
            compound: {
                must: [{ text: { query: "...", path: "text" } }],
            },
        },
    },
    { $vectorSearch: { ... } },
])

Or use $rankFusion (newer).

OSS alternative

Atlas vector search is Atlas-only. For self-hosted Mongo, use external vector DB (Qdrant, Weaviate, pgvector) or wait for OSS feature.

Embedding pipeline

def index_doc(text):
    embedding = embed(text)
    db.docs.insert_one({"text": text, "embedding": embedding})

def search(query, k=5):
    q_vec = embed(query)
    return db.docs.aggregate([
        {"$vectorSearch": {
            "index": "vector_index",
            "path": "embedding",
            "queryVector": q_vec,
            "numCandidates": 100,
            "limit": k,
        }},
    ])

Filters

Pre-filter with metadata to narrow candidates:

filter: { tenant_id: "abc", active: true }

Index filter fields explicitly in vector index definition.

Common mistakes

  • numCandidates too low → poor recall.
  • Different embedding model than corpus.
  • No filter field indexed → can’t filter.
  • Embedding dimensions mismatch.
  • Storing huge embeddings (cost vs benefit).

Read this next

If you want my Mongo+vector RAG, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .