QMD Search
unlistedby Yu-Xi Lim
BM25 full-text search, semantic vector search, and hybrid search for your vault using local embeddings.
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
| Feature | QMD Search | Omnisearch | Smart Connections | Copilot | Tezcat |
|---|---|---|---|---|---|
| Text search (BM25) | ✓ | ✓ | - | - | - |
| Semantic search | ✓ | - | ✓ | ✓ | ✓ |
| Hybrid search | ✓ (RRF fusion) | - | - | - | ✓ |
| Query expansion | ✓ | - | - | - | - |
| Reranking | ✓ | - | - | - | - |
| AI chat | - | - | ✓ | ✓ | - |
| PDF/image OCR | - | ✓ | - | - | - |
| Embedding model | Transformers.js (local) | N/A | Local or API | Local or API | Ollama or API |
| External dependencies | None | None | Optional API | Optional API | Ollama 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)
- Open Settings → Community plugins
- Search for "QMD Search"
- Install and enable
Manual Installation
- Download the latest release (
main.js,manifest.json,styles.css) - Create folder:
<your-vault>/.obsidian/plugins/obsidian-qmd/ - Copy the downloaded files into this folder
- Restart Obsidian
- Enable "QMD Search" in Settings → Community plugins
Usage
Commands
| Command | Description |
|---|---|
QMD: Open search | Open the search pane (rebind to Cmd+Shift+F if desired) |
QMD: Reindex vault | Force a full reindex of all documents |
QMD: Generate embeddings | Generate 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
- After enabling the plugin, it will automatically index your vault
- For semantic search, run
QMD: Generate embeddings(downloads the embedding model on first run) - Open the search pane with
QMD: Open search
Settings
| Setting | Description | Default |
|---|---|---|
| Default search mode | Initial mode when opening search | Hybrid |
| Auto-index on file change | Update index when files are modified | On |
| Auto-generate embeddings | Generate embeddings for new documents | Off |
| Results per page | Number of results to display | 20 |
| Group results by file | Show only best chunk per file | Off |
| Chunk size | Characters per chunk for indexing | 3200 |
| Chunk overlap | Overlap between chunks | 15% |
| Enable query expansion | Add related terms to queries | On |
| Expansion term count | Number of terms to add (1-10) | 3 |
| Enable reranking | Rerank results for better precision | On |
| Rerank top K | Number of results to rerank (5-50) | 20 |
How It Works
Indexing
- Documents are split into overlapping chunks (~3200 characters with 15% overlap)
- Heading hierarchy is extracted for each chunk (for breadcrumb display)
- Chunks are indexed in MiniSearch for fast full-text search with BM25 scoring
- Optionally, embeddings are generated using all-MiniLM-L6-v2 (384 dimensions)
- 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:
- Builds a vocabulary of terms appearing 2+ times across all chunks
- Computes embeddings for vocabulary terms (reuses the existing embedding model)
- Finds terms with highest cosine similarity to the query embedding
- 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).
| Position | Retrieval Weight | Reranker Weight |
|---|---|---|
| 1-3 | 75% | 25% |
| 4-10 | 60% | 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 search | 15-30ms |
| Vector search | 50-100ms |
| Full vault index | 30-60s |
| Embedding generation | 10-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.