MemberJunction Search — Overview Guide
MJ has multiple search APIs, each tuned for a different question. They’re complementary, not competing. This guide helps you pick the right one and points to the deeper docs for each.
First fork: is the answer inside MJ, or out on the web? Everything below the fork searches content this instance owns — entity records, vectors, full-text indexes, storage files. Searching the public web is a different system with a different result shape, a different failure mode, and a per-query cost:
@memberjunction/web-search-engine. See Web search is a different system at the end.
The decision tree
Section titled “The decision tree”What are you looking for?│├─ A specific entity definition (by exact name or ID)?│ └─ EntityByName(name) / EntityByID(id)│ Synchronous, deterministic, returns one EntityInfo.│├─ Ranked records of ONE entity, by free-text query?│ └─ Metadata.Provider.SearchEntity({ entityName, searchText, options })│ Hybrid lexical + semantic. Returns EntitySearchResult[].│ See: ENTITY_SEARCH_GUIDE.md│├─ Ranked records of MANY entities in one round-trip?│ └─ Metadata.Provider.SearchEntities([ {entityName, searchText, options}, ... ])│ Batched form of SearchEntity. Returns EntitySearchResult[][] aligned by input.│ See: ENTITY_SEARCH_GUIDE.md│├─ Full-text scan across multiple entities (no semantic, no ranking blend)?│ └─ Metadata.Provider.FullTextSearch(params)│ Uses each entity's UserSearchString rule (LIKE / FTS at the DB layer).│ See: packages/MJCore/docs/FULL_TEXT_SEARCH_GUIDE.md│├─ Cross-source unified search (vectors + FTS + entities + storage, scoped)?│ └─ SearchEngine.Search(params, contextUser) // @memberjunction/search-engine│ Multi-provider, RRF-fused, optional reranker, scope-aware.│ See: guides/SEARCH_SCOPES_AND_RAG_GUIDE.md│└─ The PUBLIC WEB, not content MJ holds? └─ WebSearchEngine.Instance.Search(params, contextUser) // @memberjunction/web-search-engine External vendors (Brave, Tavily, …) behind one interface, admin-ordered with failover. From an agent: the `Web Search` Action. From a browser: the `WebSearch.Query` remote op. See: packages/WebSearchEngine/README.mdWhat each API actually does
Section titled “What each API actually does”1. EntityByName / EntityByID — definition lookup
Section titled “1. EntityByName / EntityByID — definition lookup”On: IMetadataProvider (sync). Source: in-memory metadata maps populated at provider config. Returns: EntityInfo | undefined.
Not search. These are the canonical way to resolve “the entity called Accounts” or “the entity whose ID is …” without touching the database. O(1) lookup via the provider’s internal map. Always prefer over Entities.find(e => e.Name === ...) — that linear scan is case-sensitive and slow.
2. SearchEntity / SearchEntities — per-entity ranked record search ⭐ NEW
Section titled “2. SearchEntity / SearchEntities — per-entity ranked record search ⭐ NEW”On: IMetadataProvider (async). Source: RunView (lexical) + EntityDocument-backed vector index (semantic). Returns: EntitySearchResult[] (singular) / EntitySearchResult[][] (plural).
The use case is: “find the N records of entity X most relevant to this free-text query”. Hybrid lexical + semantic ranking, RRF-blended, post-filtered by the caller’s row-level read permissions on that entity. Backed by an EntityDocument of type Search.
Use for agent prompt seeding (swap “here’s the whole 1500-entity catalog” for “here are 10 likely candidates”), in-app “find a customer / invoice / document” UX, and any time you know the entity but don’t know which records.
Singular = one entity. Plural = many entities in one call (one GraphQL round-trip on the client; Promise.all fan-out on the server).
Canonical examples. The standardized Search EntityDocuments are the reference implementations — each backs a real, daily-synced semantic index:
| Search EntityDocument | Entity | Replaces the old bespoke path |
|---|---|---|
Actions Search | MJ: Actions | AIEngine.ActionEmbeddingService / FindSimilarActions |
AI Agents Search | MJ: AI Agents | AIEngine.AgentEmbeddingService / FindSimilarAgents |
AI Prompts Search | MJ: AI Prompts | (new — no prior bespoke path) |
Queries Search | MJ: Queries | QueryEngineServer.FindSimilarQueries |
The “Find Best Action”, “Find Candidate Actions”, “Find Best Agent”, “Find Candidate Agents”, and “Search Query Catalog” actions are now thin, backward-compatible wrappers around Provider.SearchEntity (semantic mode). New callers should use the generic “Search Entity” action — or Provider.SearchEntity directly — rather than the entity-specific wrappers.
See ENTITY_SEARCH_GUIDE.md for configuration, the seeded examples above, weighted-RRF tuning, and how to enable on your own entities.
3. FullTextSearch — multi-entity DB-level text search
Section titled “3. FullTextSearch — multi-entity DB-level text search”On: IMetadataProvider (async). Source: RunView with UserSearchString, hitting each entity’s SQL Server / PostgreSQL full-text index where configured. Returns: FullTextSearchResult grouped by entity.
Lexical only — no semantic ranking. The right choice when you want fast, broad text recall across many entities and the FTS infrastructure is already in place. Each entity opts in by setting FullTextSearchEnabled = true on its EntityInfo, which drives CodeGen DDL.
See packages/MJCore/docs/FULL_TEXT_SEARCH_GUIDE.md for entity / field configuration, CodeGen DDL behavior, permissions, and the SQL Server vs. PostgreSQL details.
4. SearchEngine.Search — cross-source unified search
Section titled “4. SearchEngine.Search — cross-source unified search”On: SearchEngine from @memberjunction/search-engine. Source: orchestrates multiple providers — vector store, full-text, entity, storage — selected via SearchScope metadata. Returns: SearchResult with per-source counts, RRF-fused matches, optional reranker scores.
The most general API. Use when the caller doesn’t know which entity (or source) to look in, or when results from multiple sources should be merged (e.g., “anything matching ‘late payment’” → invoices + email threads + uploaded contracts). Supports streaming (streamSearch), preview (PreviewSearch), and per-scope weight tuning via the shared ComputeRRF weights API.
GraphQL surface: SearchKnowledge / SearchScopes / PreviewSearch resolvers in MJServer.
See guides/SEARCH_SCOPES_AND_RAG_GUIDE.md for the scope architecture, provider configuration, RAG+ patterns, and the agent integration story.
5. WebSearchEngine.Search — the public web
Section titled “5. WebSearchEngine.Search — the public web”On: WebSearchEngine.Instance (@memberjunction/web-search-engine). Source: external
search vendors. Returns: WebSearchResult with WebSearchHit[].
await WebSearchEngine.Instance.Config(false, contextUser);const result = await WebSearchEngine.Instance.Search({ Query: 'nonprofit dues trends' }, contextUser);// result.Hits: { Title, URL, Snippet, … } result.ProviderUsed: 'Brave'Web search is a different system
Section titled “Web search is a different system”It is tempting to read SearchEngine and WebSearchEngine as two halves of one thing. They are
not, and the difference is worth understanding before reaching for either.
search-engine | web-search-engine | |
|---|---|---|
| Searches | content MJ owns | the public web |
| Result identity | EntityName + RecordID — a row you can open | a URL |
| Result type | SearchResultItem | WebSearchHit |
| Multiple sources | queried together, fused with RRF | tried in order, first success wins |
| Cost per query | a database query | real money at an external vendor |
| Failure mode | a slow query | a rate limit, an expired key, a discontinued API |
| Provider table | __mj.SearchProvider | __mj.WebSearchProvider |
Why they can’t share a result type. SearchResultItem requires ID, EntityName and
RecordID — the primary key of a source record — and the pipeline ends in SearchEnricher, which
resolves entity icons and record display names by entity lookup. A web hit has a URL and none of
those. Fitting one in would mean inventing an EntityName, which the compiler would accept and
every downstream consumer would trip over.
Why “fused” vs “in order” is the deeper split. Internal providers are cheap and complementary, so running all of them and fusing the ranks is strictly better. Web providers are expensive and substitutable — running four vendors for one query costs four times as much for one answer. So the web engine picks one, in the administrator’s priority order, and moves on only when a provider fails in a way another might survive.
What they do share is the plugin pattern: a provider table carrying DriverClass / Priority /
Status / CredentialID, ClassFactory resolution by driver key, and the
Initialize() → CheckAvailability() → IsAvailable() lifecycle. Learn one and you have read both.
Two things to know before you call it
Section titled “Two things to know before you call it”Never let an agent choose the vendor. An LLM picking between “Brave” and “Tavily” is making an
infrastructure decision it has no information for — it cannot know which key is configured, which is
cheaper, or which is rate-limited right now. All MJ agents and skills route through the provider-neutral Web Search
Action with Provider left unset so the engine can fail over across configured vendors; naming an explicit
Provider fails closed rather than failing over (so agent prompts and workflow steps should always leave it unset).
Read Attempts, including on success. If the primary provider rate-limits every call and the
secondary quietly serves everything, the system looks healthy while the spend moves to a vendor
nobody chose. result.Attempts is what makes that visible.
Why it exists at all
Section titled “Why it exists at all”Google discontinues the Custom Search JSON API on 2027-01-01 and has already closed it to new customers; Microsoft retired the Bing Search APIs on 2025-08-11. Both replaced a commodity SERP API with an answer-shaped grounding product tied to their own agent stack. The conclusion this package encodes is not “use vendor X” but that any single web-search dependency has an expiry date, so changing vendors must be a metadata edit rather than a code change.
Shared infrastructure
Section titled “Shared infrastructure”ComputeRRF — canonical weighted Reciprocal Rank Fusion
Section titled “ComputeRRF — canonical weighted Reciprocal Rank Fusion”In: @memberjunction/core. Used by: SearchEntity (hybrid mode), SearchEngine.Search (cross-source / cross-scope fusion), DuplicateRecordDetector (vector + keyword blending), and any caller that wants to combine ranked lists.
import { ComputeRRF, ScoredCandidate } from '@memberjunction/core';const fused = ComputeRRF([listA, listB], /* k */ 60, /* weights */ [1.0, 1.5]);Optional per-list weights. Omitting them gives canonical unweighted RRF (the paper-standard formula). One source of truth across all MJ search blending.
SimpleVectorServiceProvider — in-process VectorDBBase
Section titled “SimpleVectorServiceProvider — in-process VectorDBBase”In: @memberjunction/ai-vectors-memory. For: semantic ranking when you don’t want to stand up a remote vector DB. Loads EntityRecordDocument vectors lazily per EntityDocumentID — the binary VectorBinary column when valid, VectorJSON as fallback (Binary Fields Guide) — ranks via in-memory cosine. Out-of-box for the entity-catalog use case; for high-scale corpora, swap to Pinecone / Qdrant / pgvector at the EntityDocument.VectorDatabaseID level — SearchEntity’s orchestration is provider-agnostic.
EntityDocument / EntityRecordDocument
Section titled “EntityDocument / EntityRecordDocument”In: MJ core schema. What: per-entity template + per-record rendered text + persisted embedding (VectorJSON, plus the float32 VectorBinary companion that readers prefer). Drives both this guide’s SearchEntity semantic pass and the wider vector-sync pipeline used by SearchEngine vector providers, dupe detection, knowledge pipelines, etc. The same pipeline powers all of them.
Pointers to the deep docs
Section titled “Pointers to the deep docs”- Web search:
packages/WebSearchEngine/README.md— providers, failover, credentials, writing a driver
| For | Read |
|---|---|
Per-entity ranked search, configuring EntityDocuments, tuning weights | guides/ENTITY_SEARCH_GUIDE.md |
| Full-text search architecture, DDL generation, entity/field flags | packages/MJCore/docs/FULL_TEXT_SEARCH_GUIDE.md |
| Cross-source unified search, scopes, RAG+, reranking | guides/SEARCH_SCOPES_AND_RAG_GUIDE.md |
| RRF math + the weighted variant | packages/MJCore/readme.md § Weighted Reciprocal Rank Fusion |
| In-memory vector ranking primitive | packages/AI/Vectors/Memory/README.md |