QMD Search

unlisted

by Yu-Xi Lim

BM25 full-text search, semantic vector search, and hybrid search for your vault using local embeddings.

1 starsUpdated 2mo ago
View on GitHub

Obsidian QMD Search

A desktop-only Obsidian plugin that provides BM25 full-text search, semantic vector search, and hybrid search for your vault. All processing happens locally using Transformers.js for embeddings.

Inspired by QMD by Tobi Lütke - a powerful CLI-based markdown search engine. This plugin adapts the core concepts for Obsidian's browser-based environment.

Features

  • Text Search (BM25): Fast full-text search with relevance ranking
  • Semantic Search: Find conceptually similar content using local AI embeddings
  • Hybrid Search: Combines text and semantic results using Reciprocal Rank Fusion (RRF)
  • Query Expansion: Automatically adds related terms to improve recall (embedding-based)
  • Smart Reranking: Position-aware result reranking for better precision
  • Heading Breadcrumbs: Search results show section context (e.g., "Document > Section > Subsection")
  • Section Navigation: Click a result to jump directly to the relevant heading in the document
  • Section Deduplication: Automatically keeps only the best match per section (no duplicate results from the same heading)
  • Expandable Results: Click "Show more" to see full chunk content instead of snippets
  • Local Processing: All indexing and search happens on your device - no data leaves your vault
  • Auto-indexing: Automatically updates the index when files change
  • Side Pane UI: Dedicated search pane with mode selector and result previews

Comparison with Other Plugins

FeatureQMD SearchOmnisearchSmart ConnectionsCopilotTezcat
Text search (BM25)---
Semantic search-
Hybrid search✓ (RRF fusion)---
Query expansion----
Reranking----
AI chat---
PDF/image OCR----
Embedding modelTransformers.js (local)N/ALocal or APILocal or APIOllama or API
External dependenciesNoneNoneOptional APIOptional APIOllama or API
100% offline capable✓ (with local model)✓ (with local model)✓ (with Ollama)

Choose QMD Search if: You want powerful hybrid search combining keywords and meaning, with query expansion and reranking—all running locally without API keys or Ollama setup.

Choose Omnisearch if: You only need fast text search with PDF/image OCR support.

Choose Smart Connections/Copilot if: You want AI chat features to interact with your notes conversationally.

Installation

From Obsidian Community Plugins (Coming Soon)

  1. Open Settings → Community plugins
  2. Search for "QMD Search"
  3. Install and enable

Manual Installation

  1. Download the latest release (main.js, manifest.json, styles.css)
  2. Create folder: <your-vault>/.obsidian/plugins/obsidian-qmd/
  3. Copy the downloaded files into this folder
  4. Restart Obsidian
  5. Enable "QMD Search" in Settings → Community plugins

Usage

Commands

CommandDescription
QMD: Open searchOpen the search pane (rebind to Cmd+Shift+F if desired)
QMD: Reindex vaultForce a full reindex of all documents
QMD: Generate embeddingsGenerate semantic embeddings for all documents

Search Modes

  • Hybrid (default): Combines text and semantic search for best results
  • Text only: Traditional keyword search using BM25 ranking
  • Semantic only: Find conceptually similar content regardless of exact wording

First-Time Setup

  1. After enabling the plugin, it will automatically index your vault
  2. For semantic search, run QMD: Generate embeddings (downloads the embedding model on first run)
  3. Open the search pane with QMD: Open search

Settings

SettingDescriptionDefault
Default search modeInitial mode when opening searchHybrid
Auto-index on file changeUpdate index when files are modifiedOn
Auto-generate embeddingsGenerate embeddings for new documentsOff
Results per pageNumber of results to display20
Group results by fileShow only best chunk per fileOff
Chunk sizeCharacters per chunk for indexing3200
Chunk overlapOverlap between chunks15%
Enable query expansionAdd related terms to queriesOn
Expansion term countNumber of terms to add (1-10)3
Enable rerankingRerank results for better precisionOn
Rerank top KNumber of results to rerank (5-50)20

How It Works

Indexing

  1. Documents are split into overlapping chunks (~3200 characters with 15% overlap)
  2. Heading hierarchy is extracted for each chunk (for breadcrumb display)
  3. Chunks are indexed in MiniSearch for fast full-text search with BM25 scoring
  4. Optionally, embeddings are generated using all-MiniLM-L6-v2 (384 dimensions)
  5. Embeddings are stored in hnswlib-wasm for efficient O(log n) similarity search

Search

  • Query expansion (optional): Before searching, finds semantically similar terms from your vault's vocabulary and adds them to the query. Original query is weighted 2x.
  • Text search: Uses MiniSearch's inverted index with proper BM25 ranking, fuzzy matching, and prefix search
  • Semantic search: Generates query embedding and finds similar chunks via hnswlib's HNSW algorithm
  • Hybrid search: Runs both searches, combines results using RRF (see below)
  • Reranking (optional): Applies position-aware blending using semantic similarity scores (see below)
  • Section deduplication: For semantic and hybrid modes, multiple chunks from the same heading section are deduplicated, keeping only the highest-scoring result per section.

Combined Ranking

Hybrid search uses a multi-stage pipeline:

Stage 0: Query Expansion (Optional)

Before searching, the query is expanded with semantically similar terms from your vault's vocabulary. The expansion service:

  1. Builds a vocabulary of terms appearing 2+ times across all chunks
  2. Computes embeddings for vocabulary terms (reuses the existing embedding model)
  3. Finds terms with highest cosine similarity to the query embedding
  4. Adds top N terms to the query, with original terms weighted 2x

Example: Query "machine learning" might expand to "machine learning neural network deep training".

Stage 1: Reciprocal Rank Fusion (RRF)

RRF merges ranked lists without requiring score normalization. Each result's score is computed as:

RRF_score = Σ (1 / (k + rank))

Where k=60 (a smoothing constant) and rank is the result's position in each list. Results appearing in both text and semantic results get scores from both, naturally boosting documents that rank well in multiple retrieval methods.

Stage 2: Position Bonuses

Top-ranked results receive small bonuses to reward consistent high placement:

  • Rank 1: +0.05
  • Ranks 2-3: +0.02

This gives a slight edge to results that both retrieval methods agree are highly relevant.

Stage 3: Reranking (Optional)

After RRF merge, reranking applies position-aware blending using the semantic similarity scores. The intuition: top results from retrieval are likely correct (trust retrieval more), while lower results benefit from reranker judgment (trust semantic similarity more).

PositionRetrieval WeightReranker Weight
1-375%25%
4-1060%40%
11+40%60%

Final score: weight_retrieval × RRF_score + weight_reranker × vector_score

This tiered approach preserves high-confidence matches while allowing the reranker to surface semantically relevant results that may have ranked lower initially.

Storage

  • Text index: MiniSearch in-memory index persisted to IndexedDB as JSON
  • Vector index: hnswlib-wasm HNSW index with IndexedDB persistence
  • Indexes are stored per-device (not synced) - each device builds its own index

Development

# Install dependencies
npm install

# Run tests
npm test

# Build plugin
npm run build

# Development mode (watch)
npm run dev

# Lint
npm run lint

Project Structure

src/
├── main.ts                  # Plugin entry point
├── settings.ts              # Settings management
├── storage/
│   ├── DocumentStore.ts     # MiniSearch full-text search
│   └── VectorStore.ts       # hnswlib-wasm vector search
├── indexing/
│   ├── Chunker.ts           # Document chunking
│   └── IndexingService.ts   # Index orchestration
├── search/
│   ├── SearchService.ts     # Unified search (BM25 + vector + hybrid)
│   └── RerankingService.ts  # Position-aware result reranking
├── llm/
│   └── QueryExpansionService.ts # Embedding-based query expansion
├── embeddings/
│   └── EmbeddingService.ts  # Transformers.js wrapper
└── ui/
    ├── SearchView.ts        # Side pane view
    └── SettingsTab.ts       # Settings UI

Performance

Operation~1000 documents
Text search15-30ms
Vector search50-100ms
Full vault index30-60s
Embedding generation10-30s per 100 docs

Requirements

  • Obsidian 1.0.0 or later
  • Desktop only (macOS, Windows, Linux)

License

MIT

Credits

  • QMD by Tobi Lütke - The original CLI search engine that inspired this plugin
  • MiniSearch - Tiny but powerful full-text search with BM25 scoring
  • hnswlib-wasm - WebAssembly port of hnswlib for fast vector search
  • Transformers.js - Run transformer models in the browser
  • all-MiniLM-L6-v2 - Sentence embedding model

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.