Relevant source files
The following files were used as context for generating this wiki page:
- packages/core/src/search/client/orama-cloud.ts
- packages/base-ui/src/components/dialog/search-orama.tsx
- packages/radix-ui/src/components/dialog/search-orama.tsx
- packages/base-ui/src/components/dialog/search-algolia.tsx
- packages/radix-ui/src/components/dialog/search-algolia.tsx
- packages/base-ui/src/components/dialog/search-default.tsx
- packages/radix-ui/src/components/dialog/search-default.tsx
- packages/core/src/search/client/orama-cloud-legacy.ts
- packages/core/src/search/client.ts
- packages/core/src/search/orama/create-server.ts
- packages/core/src/search/client/mixedbread.ts
- packages/core/src/search/mixedbread.ts
- packages/core/src/search/client/orama-static.ts
- packages/create-app/template/+orama-cloud/@app/components/search.tsx
- packages/base-ui/src/components/dialog/search.tsx
- packages/radix-ui/src/components/dialog/search.tsx
- packages/core/src/search/client/fetch.ts
- packages/preview/src/components/ai/search.tsx
- packages/core/src/search/client/flexsearch-static.ts
- packages/core/src/search/client/algolia.ts
- packages/create-app/src/plugins/orama-cloud.ts
- packages/core/src/search/flexsearch.ts
- packages/base-ui/src/contexts/search.tsx
- packages/radix-ui/src/contexts/search.tsx
- packages/core/src/search/server.ts
- packages/core/src/search/orama-cloud-legacy.ts
- packages/core/src/search/orama-cloud.ts
- packages/core/src/search/orama/create-db.ts
- packages/core/src/search/orama/search/advanced.ts
- packages/core/src/search/orama/search/simple.ts
Overview
Search Client Integrations provide client-side query execution, state coordination, and UI bindings for documentation search across multiple search engines. They bridge the gap between user input inside search dialogs and backend or in-browser search indexes, handling debouncing, result caching, dependency tracking, and asynchronous client loading.
Sources: packages/core/src/search/client.ts:78-197
Search Client React Hook Architecture
Hook API and State Structure
The useDocsSearch hook manages user search input, debounced query execution, error handling, loading states, and result caching across different search provider backends. It accepts clientOptions containing preset configurations or a direct SearchClient instance, alongside an optional customDeps dependency list.
export function useDocsSearch(
clientOptions: ClientPreset & {
/**
* The debounced delay for performing a search (in ms).
* .
* @defaultValue 100
*/
delayMs?: number;
/**
* still perform search even if query is empty.
*
* @defaultValue false
*/
allowEmpty?: boolean;
},
customDeps?: DependencyList,
): UseDocsSearch {Sources: packages/core/src/search/client.ts:83-100
The return value conforms to the UseDocsSearch interface, exposing the raw input string, its setter, and a query status object holding loading flags, result arrays, or errors.
interface UseDocsSearch {
search: string;
setSearch: (v: string) => void;
query: {
isLoading: boolean;
data?: SortedResult[] | 'empty';
error?: Error;
};
}Sources: packages/core/src/search/client.ts:14-22
Client Preset Resolution and Deprecation
ClientPreset supports both legacy string-keyed configuration types and modern direct SearchClient references. When a legacy type string is provided, useDocsSearch dynamically imports the respective client module and constructs the search client asynchronously.
export type ClientPreset =
| ({
/**
* @deprecated Pass `client: fetchClient(...)` instead.
*/
type: 'fetch';
} & FetchOptions)
| ({
/**
* @deprecated Pass `client: oramaStaticClient(...)` instead.
*/
type: 'static';
} & StaticOptions)
| ({
/**
* @deprecated Pass `client: algoliaClient(...)` instead.
*/
type: 'algolia';
} & AlgoliaOptions)
| ({
/**
* @deprecated Pass `client: oramaCloudClient(...)` instead.
*/
type: 'orama-cloud';
} & OramaCloudOptions)
| ({
/**
* @deprecated Pass `client: oramaCloudLegacyClient(...)` instead.
*/
type: 'orama-cloud-legacy';
} & OramaCloudLegacyOptions)
| ({
/**
* @deprecated Pass `client: flexsearchStaticClient(...)` instead.
*/
type: 'flexsearch-static';
} & FlexsearchStaticOptions)
| ({
/**
* @deprecated Use `createMixedbreadSearchAPI` from `fumadocs-core/search/mixedbread` instead.
* This client-side approach exposes your API key in the browser.
* The server-side approach keeps the key secure and uses `client: fetchClient(...)` on the client.
*/
type: 'mixedbread';
} & MixedbreadOptions)
| {
client: SearchClient;
};Sources: packages/core/src/search/client.ts:24-71
| Preset Type | Module Import Path | Options Interface |
|---|---|---|
fetch | @/search/client/fetch | FetchOptions |
static | @/search/client/orama-static | StaticOptions |
algolia | @/search/client/algolia | AlgoliaOptions |
orama-cloud | @/search/client/orama-cloud | OramaCloudOptions |
orama-cloud-legacy | @/search/client/orama-cloud-legacy | OramaCloudLegacyOptions |
flexsearch-static | ./client/flexsearch-static | FlexsearchStaticOptions |
mixedbread | @/search/client/mixedbread | MixedbreadOptions |
Sources: packages/core/src/search/client.ts:110-143
Warning
Using string-keyed presets like type: 'fetch' is deprecated. Pass a pre-instantiated client such as client: fetchClient(...) directly to avoid runtime dynamic imports and ensure type-safe dependency tracking.
Sources: packages/core/src/search/client.ts:24-71
Debouncing and Task Interruption
Search input is processed through useDebounce with a configurable delayMs defaulting to 100 milliseconds. Setting allowEmpty to true forces search execution even when the query string is empty.
const { delayMs = 100, allowEmpty = false, ...clientRest } = clientOptions;
const [search, setSearch] = useState('');
const [results, setResults] = useState<SortedResult[] | 'empty'>('empty');
const [error, setError] = useState<Error>();
const [isLoading, setIsLoading] = useState(false);
const debouncedValue = useDebounce(search, delayMs);Sources: packages/core/src/search/client.ts:101-106
Active search tasks are tracked via activeTaskRef. When dependencies or debounced values change before an in-flight search resolves, the previous task is immediately marked as interrupted, preventing stale responses from overwriting newer query results.
const [activeDeps, setActiveDeps] = useState<DependencyList | null>(null);
const activeTaskRef = useRef<{
deps: DependencyList;
start: () => void;
interrupt: boolean;
} | null>(null);
if (
!isEqualShallow(activeDeps, deps) &&
(!activeTaskRef.current || !isEqualShallow(activeTaskRef.current.deps, deps))
) {
if (activeTaskRef.current) activeTaskRef.current.interrupt = true;
activeTaskRef.current = {
deps,
interrupt: false,
async start() {
try {
setIsLoading(true);
let res: SortedResult[] | 'empty';
if (debouncedValue.length === 0 && !allowEmpty) res = 'empty';
else res = await (await client).search(debouncedValue);
if (!this.interrupt) {
setActiveDeps(deps);
setError(undefined);
setResults(res);
}
} catch (err) {
if (!this.interrupt) setError(err as Error);
} finally {
if (!this.interrupt) setIsLoading(false);
}
},
};
void activeTaskRef.current.start();
}Sources: packages/core/src/search/client.ts:154-191
Note
If allowEmpty is false and the debounced query length is zero, the hook bypasses the search client call entirely and sets the result data directly to 'empty'.
Sources: packages/core/src/search/client.ts:174-176
Query Caching via Fetch Client
The built-in fetchClient utilizes an internal Map cache (globalCache by default, or a custom map passed via FetchOptions) keyed by the fully constructed request URL string. Before executing a network request, it checks for a cached SortedResult[] entry.
const globalCache = new Map();
export function fetchClient({
api = '/api/search',
locale,
tag,
cache = globalCache,
}: FetchOptions = {}): SearchClient {
return {
deps: [api, locale, tag],
async search(query) {
const url = new URL(api, window.location.origin);
url.searchParams.set('query', query);
if (locale) url.searchParams.set('locale', locale);
if (tag) url.searchParams.set('tag', Array.isArray(tag) ? tag.join(',') : tag);
const key = url.toString();
const cached = cache.get(key);
if (cached) return cached;
const res = await fetch(url);
if (!res.ok) throw new Error(await res.text());
const result = (await res.json()) as SortedResult[];
cache.set(key, result);
return result;
},
};Sources: packages/core/src/search/client/fetch.ts:25-51
Default Route Querying via Fetch
Fetch Client Configuration and Parameters
The fetchClient factory function constructs a client adhering to the SearchClient interface by querying an API endpoint over HTTP. It accepts FetchOptions to customize the network request behavior and parameter serialization.
| Option Key | Type | Default Value | Purpose |
|---|---|---|---|
api | string | '/api/search' | API route for the search endpoint, supporting absolute URLs. |
tag | string | string[] | undefined | Filter results with one or more tags (comma-joined if an array). |
locale | string | undefined | Filter search results by language locale. |
cache | Map<string, SortedResult[]> | globalCache | Storage map used for query caching across searches. |
Sources: packages/core/src/search/client/fetch.ts:4-23, packages/core/src/search/client/fetch.ts:25-32
When search(query) is invoked, fetchClient builds a URL instance using window.location.origin as the base, appending query parameters for query, locale, and tag. If tag is provided as an array, its elements are joined with commas.
async search(query) {
const url = new URL(api, window.location.origin);
url.searchParams.set('query', query);
if (locale) url.searchParams.set('locale', locale);
if (tag) url.searchParams.set('tag', Array.isArray(tag) ? tag.join(',') : tag);
Sources: packages/core/src/search/client/fetch.ts:35-40
Warning
If the fetch response status is not OK (!res.ok), fetchClient reads the response body as text and throws it immediately as an Error.
Sources: packages/core/src/search/client/fetch.ts:45-46
Default Search Dialog Integration
The DefaultSearchDialog component integrates fetchClient with Base UI Components and Radix primitives, wrapping useDocsSearch to power accessible search dialogs.
export default function DefaultSearchDialog({
defaultTag,
tags = [],
api,
delayMs,
allowClear = false,
links = [],
footer,
...props
}: DefaultSearchDialogProps) {
const { locale } = useI18n();
const [tag, setTag] = useState(defaultTag);
const { search, setSearch, query } = useDocsSearch({
client: fetchClient({
api,
locale,
tag,
}),
delayMs,
});Sources: packages/radix-ui/src/components/dialog/search-default.tsx:50-69
| Prop Name | Type | Default Value | Description |
|---|---|---|---|
links | SearchLink[] | [] | Default fallback links displayed when no search query is active. |
defaultTag | string | undefined | Initial active filter tag. |
tags | TagItem[] | [] | Available filter tags rendered in the dialog footer. |
api | string | undefined | Custom search API URL passed to fetchClient. |
delayMs | number | undefined | Debounce delay for executing search requests. |
allowClear | boolean | false | Allows users to clear active tag filters. |
footer | ReactNode | undefined | Custom React node rendered alongside the tag list in the footer. |
Sources: packages/radix-ui/src/components/dialog/search-default.tsx:25-48
DefaultSearchDialog synchronizes the dialog's tag state with locale changes and passes the resulting query items to SearchDialogList. When the query data evaluates to 'empty', it falls back to rendering the memoized default links converted into SortedResult items.
const defaultItems = useMemo<SortedResult[] | null>(() => {
if (links.length === 0) return null;
return links.map(([name, link]) => ({
type: 'page',
id: name,
content: name,
url: link,
}));
}, [links]);Sources: packages/radix-ui/src/components/dialog/search-default.tsx:70-78
<SearchDialogList items={query.data !== 'empty' ? query.data : defaultItems} />Sources: packages/radix-ui/src/components/dialog/search-default.tsx:93-93
Tip
If tags array length is greater than zero, DefaultSearchDialog automatically renders a TagsList component inside SearchDialogFooter equipped with tag selection handlers and clear options.
Sources: packages/radix-ui/src/components/dialog/search-default.tsx:96-104
Orama Cloud Client Adapters
Query Pipeline and Index Modes
Fumadocs provides client adapters for both modern Orama Cloud (@orama/core) and legacy Orama clients (@oramacloud/client) via oramaCloudClient and oramaCloudLegacyClient. Each adapter constructs a SearchClient configured with dependencies and an asynchronous search(query) execution pipeline.
export function oramaCloudClient(options: OramaCloudOptions): SearchClient {
const { index = 'default', client, params: extraParams, tag } = options;
return {
deps: [index, client, tag],Sources: packages/core/src/search/client/orama-cloud.ts:40-44
export function oramaCloudLegacyClient(options: OramaCloudLegacyOptions): SearchClient {
const { index = 'default', client, params: extraParams = {}, tag } = options;
return {
deps: [index, client, tag],Sources: packages/core/src/search/client/orama-cloud-legacy.ts:40-44
The client supports two indexing strategies: default (structured Fumadocs JSON indexes) and crawler (web crawler indexes). When index is set to 'crawler', search queries target the web crawler datasource, grouping results into page and text items.
if (index === 'crawler') {
const result = await client.search({
datasources: [],
...extraParams,
term: query,
where: {
category: tag
? {
eq: tag.slice(0, 1).toUpperCase() + tag.slice(1),
}
: undefined,
...extraParams?.where,
},
limit: 10,
});Sources: packages/core/src/search/client/orama-cloud.ts:49-63
In default mode, queries invoke client.search with a maximum limit of 20 results, grouping matches by page_id with up to 7 results per group. The resulting grouped hits are mapped into SortedResult objects using createContentHighlighter to process markdown highlights.
const result = await client.search({
datasources: [],
...extraParams,
term: query,
limit: 20,
where: removeUndefined({
tag,
...extraParams?.where,
}),
groupBy: {
properties: ['page_id'],
max_results: 7,
...extraParams?.groupBy,
},
});Sources: packages/core/src/search/client/orama-cloud.ts:88-102
Warning
In oramaCloudClient, if the total mapped result list exceeds 80 items, the client slices the array to a maximum length of 80 before returning.
Sources: packages/core/src/search/client/orama-cloud.ts:131-131
Orama Search Dialog Integration
The OramaSearchDialog component integrates Orama Cloud search into application dialogs using Base UI Components, accepting client instances, parameters, and tag configurations.
export default function OramaSearchDialog({
client,
searchOptions,
tags = [],
defaultTag,
showOrama = true,
allowClear = false,
index,
footer,
links = [],
...props
}: OramaSearchDialogProps) {
const { locale } = useI18n();
const [tag, setTag] = useState(defaultTag);
const { search, setSearch, query } = useDocsSearch({
client: oramaCloudClient({
client,
index,
params: searchOptions,
locale,
tag,
}),
});Sources: packages/base-ui/src/components/dialog/search-orama.tsx:54-76
| Prop Name | Type | Default Value | Description |
|---|---|---|---|
client | OramaCloudOptions['client'] | — | Orama Cloud client instance. |
searchOptions | OramaCloudOptions['params'] | undefined | Additional search parameters. |
index | OramaCloudOptions['index'] | undefined | Index type ('default' or 'crawler'). |
tags | TagItem[] | [] | List of filter tags for the dialog footer. |
defaultTag | string | undefined | Initial active filter tag. |
showOrama | boolean | true | Controls the visibility of the "Search powered by Orama" branding label. |
allowClear | boolean | false | Allows users to clear active tag filters. |
links | SearchLink[] | [] | Fallback links when no search query is active. |
Sources: packages/base-ui/src/components/dialog/search-orama.tsx:25-49
Static Orama and Flexsearch Engines
Static Orama Client Implementation
Fumadocs provides client execution for in-browser static search indices through oramaStaticClient. The client accepts StaticOptions configuration parameters governing index fetching, schema initialization, tag filtering, and localized database management.
export interface StaticOptions {
/**
* Where to download exported search indexes (URL)
*
* @defaultValue '/api/search'
*/
from?: string;
initOrama?: (locale?: string) => AnyOrama | Promise<AnyOrama>;
/**
* Filter results with specific tag(s).
*/
tag?: string | string[];
/**
* Filter by locale (unsupported at the moment)
*/
locale?: string;
/**
* extra options for search
*/
search?: Partial<SearchParams<Orama<unknown>>>;
}Sources: packages/core/src/search/client/orama-static.ts:8-32
| Option Name | Type | Default Value | Description |
|---|---|---|---|
from | string | '/api/search' | URL endpoint to download exported search indexes. |
initOrama | (locale?: string) => AnyOrama | Promise<AnyOrama> | create({ schema: { _: 'string' }, language: locale }) | Custom Orama instance initializer per locale. |
tag | string | string[] | undefined | Filter results by specific tag(s). |
locale | string | undefined | Filter by locale. |
search | Partial<SearchParams<Orama<unknown>>> | undefined | Extra parameters for search execution. |
Sources: packages/core/src/search/client/orama-static.ts:8-32
Database loading is handled asynchronously via loadDB, which fetches exported index payloads and populates an in-memory database map cache keyed by locale. When internationalization data types are encountered, instances are built per locale entry, whereas single-index payloads initialize a default empty locale key.
async function loadDB(
from: string,
initOrama: StaticOptions['initOrama'] = (locale) =>
create({ schema: { _: 'string' }, language: locale }),
): Promise<Database> {
const res = await fetch(from);
if (!res.ok)
throw new Error(
`failed to fetch exported search indexes from ${from}, make sure the search database is exported and available for client.`,
);
const data = (await res.json()) as ExportedData;
const dbs: Database = new Map();
if (data.type === 'i18n') {
await Promise.all(
Object.entries(data.data).map(async ([k, v]) => {
const db = await initOrama(k);
load(db, v);
dbs.set(k, {
type: v.type,
db,
});
}),
);
} else {
const db = await initOrama();
load(db, data);
dbs.set('', {
type: data.type,
db,
});
}
return dbs;
}Sources: packages/core/src/search/client/orama-static.ts:45-82
Warning
If a requested locale is missing from the loaded database collection, oramaStaticClient logs a warning message and falls back to the first available database value in the map.
Sources: packages/core/src/search/client/orama-static.ts:104-109
Execution branches between simple and advanced search helpers depending on the database type property ('simple' or 'advanced'). Simple searches delegate to searchSimple with tolerance and title boost configurations, while advanced searches use searchAdvanced with groupBy page ID properties and tag filters.
export async function searchSimple(
db: Orama<typeof simpleSchema>,
query: string,
params: Partial<SearchParams<Orama<typeof simpleSchema>, SimpleDocument>> = {},
): Promise<SortedResult[]> {
const highlighter = createContentHighlighter(query);
const result = await search(db, {
term: query,
tolerance: 1,
...params,
boost: {
title: 2,
...('boost' in params ? params.boost : undefined),
},
});Sources: packages/core/src/search/orama/search/simple.ts:5-19
export async function searchAdvanced(
db: Orama<typeof advancedSchema>,
query: string,
tag: string | string[] = [],
{
mode = 'fulltext',
...override
}: Partial<SearchParams<Orama<typeof advancedSchema>, AdvancedDocument>> = {},
): Promise<SortedResult[]> {
if (typeof tag === 'string') tag = [tag];
const params = {
limit: 60,
mode,
...override,
where: removeUndefined({
tags:
tag.length > 0
? {
containsAll: tag,
}
: undefined,
...override.where,
}),
groupBy: {
properties: ['page_id'],
maxResult: 8,
...override.groupBy,
},
properties: mode === 'fulltext' ? ['content'] : ['content', 'embeddings'],
} as SearchParams<typeof db, AdvancedDocument>;Sources: packages/core/src/search/orama/search/advanced.ts:6-36
Static Flexsearch Client Implementation
Local in-browser client execution using Flexsearch is provided by flexsearchStaticClient. The client accepts FlexsearchStaticOptions to configure endpoint fetching, default locales, and tags.
export interface FlexsearchStaticOptions {
/**
* @defaultValue `/api/search`
*/
from?: string;
locale?: string;
tag?: string | string[];
}Sources: packages/core/src/search/client/flexsearch-static.ts:6-13
| Option Name | Type | Default Value | Description |
|---|---|---|---|
from | string | '/api/search' | URL endpoint to download exported Flexsearch indexes. |
locale | string | '' | Target locale key for querying. |
tag | string | string[] | undefined | Optional tag filter for search queries. |
Sources: packages/core/src/search/client/flexsearch-static.ts:6-13
The initialization routine checks whether window is defined before executing the asynchronous fetch function init. It parses exported JSON payloads, iterating over internationalized raw records or standard document maps to instantiate Flexsearch document wrappers via initDocument.
function initDocument(data: Record<string, string>) {
const document = createDocument();
for (const [k, v] of Object.entries(data)) document.import(k, v);
return document;
}Sources: packages/core/src/search/client/flexsearch-static.ts:15-19
async function init(from: string) {
const res = await fetch(from);
if (!res.ok)
throw new Error(
`failed to fetch exported search indexes from ${from}, make sure the search database is exported and available for client.`,
);
const data = (await res.json()) as ExportedData;
const dbs = new Map<string, Document<Doc>>();
if (data.type === 'i18n') {
for (const [locale, map] of Object.entries(data.raw)) {
dbs.set(locale, initDocument(map));
}
return dbs;
} else {
dbs.set('', initDocument(data.raw));
}
return dbs;Sources: packages/core/src/search/client/flexsearch-static.ts:43-64
Note
flexsearchStaticClient registers dependency tracking for from, locale, and tag, returning an empty array if the requested locale database has not been loaded into the map cache.
Sources: packages/core/src/search/client/flexsearch-static.ts:33-39
Algolia Search Client and Grouping
Algolia Lite Client Adapter
The Algolia client implementation provides search integration via algoliaClient, accepting AlgoliaOptions to configure the target index, search client instance, tag filters, locale, and an optional custom onSearch handler function.
export interface AlgoliaOptions {
indexName: string;
client: LiteClient;
/**
* Filter results with specific tag.
*/
tag?: string;
locale?: string;
onSearch?: (
query: string,
tag?: string,
locale?: string,
) => Promise<{
results: SearchResponse<BaseIndex>[];
}>;
}Sources: packages/core/src/search/client/algolia.ts:6-24
| Option Name | Type | Default Value | Description |
|---|---|---|---|
indexName | string | required | Target Algolia index name. |
client | LiteClient | required | Algolia lite client instance. |
tag | string | undefined | Optional tag filter string (tag:${tag}). |
locale | string | undefined | Optional locale identifier passed to custom search handlers. |
onSearch | Function | undefined | Custom search function overriding default requests. |
Sources: packages/core/src/search/client/algolia.ts:6-24
Warning
If the search query string contains only whitespace or is empty after trimming (query.trim().length === 0), algoliaClient immediately returns an empty array without executing a network request.
Sources: packages/core/src/search/client/algolia.ts:58-59
Hierarchical Hit Grouping and Execution Flow
When executing a search request, algoliaClient checks whether a custom onSearch function is provided; otherwise, it invokes client.searchForHits with a request configuration containing a distinct value of 5, hitsPerPage set to 10, and an optional tag filter. The resulting hits are processed by groupResults to construct a hierarchical list of pages, headings, and text blocks.
const result = onSearch
? await onSearch(query, tag, locale)
: await client.searchForHits<BaseIndex>({
requests: [
{
type: 'default',
indexName,
query,
distinct: 5,
hitsPerPage: 10,
filters: tag ? `tag:${tag}` : undefined,
},
],
});
Sources: packages/core/src/search/client/algolia.ts:61-75
Sources: packages/base-ui/src/components/dialog/search-algolia.tsx:61-67, packages/core/src/search/client/algolia.ts:26-52, packages/core/src/search/client/algolia.ts:58-87
The grouping function groupResults tracks scanned URLs in a Set<string>. For each unique URL encountered, it pushes a parent page result into the accumulator array before appending individual section or text hits with constructed anchor URLs.
function groupResults(hits: Hit<BaseIndex>[]): SortedResult[] {
const grouped: SortedResult[] = [];
const scannedUrls = new Set<string>();
for (const hit of hits) {
if (!scannedUrls.has(hit.url)) {
scannedUrls.add(hit.url);
grouped.push({
id: hit.url,
type: 'page',
breadcrumbs: hit.breadcrumbs,
url: hit.url,
content: hit.title,
});
}
grouped.push({
id: hit.objectID,
type: hit.content === hit.section ? 'heading' : 'text',
url: hit.section_id ? `${hit.url}#${hit.section_id}` : hit.url,
content: hit.content,
});
}
return grouped;
}Sources: packages/core/src/search/client/algolia.ts:26-52
Mixedbread Client Vector Retrieval
Mixedbread Options and Heading Transformations
The Mixedbread client integration supports vector search queries against a remote store via mixedbreadClient or server-side endpoints through createMixedbreadSearchAPI. MixedbreadOptions defines the configuration required to connect to a store instance using the Mixedbread SDK client.
export interface MixedbreadOptions {
/**
* The identifier of the store to search in
*/
storeIdentifier: string;
/**
* The Mixedbread SDK client instance
*/
client: Mixedbread;
/**
* Filter results with specific tag.
*/
tag?: string;
/**
* Filter by locale (unsupported at the moment)
*/
locale?: string;
}Sources: packages/core/src/search/client/mixedbread.ts:8-28
| Option Name | Type | Default Value | Description |
|---|---|---|---|
storeIdentifier | string | required | The identifier of the store to search in. |
client | Mixedbread | required | The Mixedbread SDK client instance. |
tag | string | undefined | Filter results with a specific tag. |
locale | string | undefined | Filter by locale (unsupported at the moment). |
Sources: packages/core/src/search/client/mixedbread.ts:8-28
Warning
mixedbreadClient is deprecated because running client-side SDK queries exposes your API key in the browser. The recommended approach is to use createMixedbreadSearchAPI on the server and connect with a fetch client on the client.
Sources: packages/core/src/search/client/mixedbread.ts:63-67
Search Execution and Chunk Processing
When a search query is submitted, mixedbreadClient queries client.stores.search requesting a top-k limit of 10 items with metadata enabled and tag filtering applied via generated_metadata.tag. Each returned search result chunk is expanded into structured page and heading results.
async search(query) {
if (!query.trim()) {
return [];
}
const res = await client.stores.search({
query,
store_identifiers: [storeIdentifier],
top_k: 10,
filters: {
key: 'generated_metadata.tag',
operator: 'eq',
value: tag,
},
search_options: {
return_metadata: true,
},
});Sources: packages/core/src/search/client/mixedbread.ts:73-90
return (res.data as StoreSearchResult[]).flatMap((item) => {
const metadata = item.generated_metadata;
const url = metadata.url || '#';
const title = metadata.title || 'Untitled';
const chunkResults: SortedResult[] = [
{
id: `${item.file_id}-${item.chunk_index}-page`,
type: 'page',
content: title,
url,
},
];
const headingTitle =
item.type === 'text' && item.text ? extractHeadingTitle(item.text) : '';
if (headingTitle) {
slugger.reset();
chunkResults.push({
id: `${item.file_id}-${item.chunk_index}-heading`,
type: 'heading',
content: headingTitle,
url: `${url}#${slugger.slug(headingTitle)}`,
});
}
return chunkResults;
});Sources: packages/core/src/search/client/mixedbread.ts:92-122
The helper function extractHeadingTitle inspects text content to verify whether it starts with a markdown heading indicator (#). If it does, the first line is split, cleaned with removeMarkdown with image alt text disabled, and slugified using github-slugger for anchor URL generation.
function extractHeadingTitle(text: string): string {
const trimmedText = text.trim();
if (!trimmedText.startsWith('#')) {
return '';
}
const lines = trimmedText.split('\n');
const firstLine = lines[0]?.trim();
if (firstLine) {
// Use remove-markdown to convert to plain text and remove colons
return removeMd(firstLine, {
useImgAltText: false,
});
}
return '';
}Sources: packages/core/src/search/client/mixedbread.ts:43-61
Note
slugger.reset() is invoked before processing each heading title to ensure deterministic slug generation across chunk result iterations.
Sources: packages/core/src/search/client/mixedbread.ts:111-111
Search Context and Keyboard Shortcuts
Global Search Context and Provider Architecture
The SearchProvider component establishes global search state using SearchContext, exposing options for dialog preloading, custom empty-state links, and hotkey bindings. When preload is enabled (defaulting to true), the search dialog state is initialized to undefined until the dialog is opened, triggering lazy loading of DefaultSearchDialog.
export function SearchProvider({
SearchDialog = DefaultSearchDialog,
children,
preload = true,
options,
hotKey = DEFAULT_HOT_KEYS,
links,
}: SearchProviderProps) {
const [isOpen, setIsOpen] = useState(preload ? false : undefined);
const onKeyDown = useEffectEvent((e: KeyboardEvent) => {
if (hotKey.every((v) => (typeof v.key === 'string' ? e.key === v.key : v.key(e)))) {
setIsOpen((open) => !open);
e.preventDefault();
}
});
useEffect(() => {
window.addEventListener('keydown', onKeyDown);
return () => {
window.removeEventListener('keydown', onKeyDown);
};
}, []);
return (
<SearchContext
value={useMemo(
() => ({
enabled: true,
open: isOpen ?? false,
hotKey,
setOpenSearch: setIsOpen,
}),
[isOpen, hotKey],
)}
>
<Suspense fallback={null}>
{isOpen !== undefined && (
<SearchDialog
open={isOpen}
onOpenChange={setIsOpen}
// @ts-expect-error -- insert prop for official UIs
links={links}
{...options}
/>
)}
</Suspense>
{children}
</SearchContext>
);
}Sources: packages/base-ui/src/contexts/search.tsx:112-162
| Property | Type | Default Value | Description |
|---|---|---|---|
preload | boolean | true | Preload search dialog before opening it. |
links | SearchLink[] | undefined | Custom links to be displayed if search is empty. |
hotKey | HotKey[] | DEFAULT_HOT_KEYS | Hotkeys for triggering the search dialog. |
SearchDialog | ComponentType<SharedProps> | DefaultSearchDialog | Replace default search dialog (e.g. Algolia Search). |
options | Partial<SharedProps & Record<string, unknown>> | undefined | Additional properties passed to the dialog. |
Sources: packages/base-ui/src/contexts/search.tsx:36-69
Note
The SearchOnly wrapper component inspects search.enabled from the context and renders its children only when search has been successfully initialized via a provider.
Sources: packages/base-ui/src/contexts/search.tsx:164-171
Hotkey Bindings and Platform Detection
Keyboard shortcuts are managed via global window event listeners registered inside SearchProvider. The default hotkey configuration DEFAULT_HOT_KEYS combines a modifier key condition with the k key.
const DEFAULT_HOT_KEYS: HotKey[] = [
{
key: (e) => e.metaKey || e.ctrlKey,
display: <MetaOrControl />,
},
{
key: 'k',
display: 'K',
},
];Sources: packages/base-ui/src/contexts/search.tsx:99-108
The MetaOrControl helper component checks window.navigator.userAgent against a regular expression testing for Windows or Linux platforms to dynamically switch the displayed shortcut symbol between ⌘ and Ctrl.
function MetaOrControl() {
const [key, setKey] = useState('⌘');
useEffect(() => {
if (/Windows|Linux/i.test(window.navigator.userAgent)) setKey('Ctrl');
}, []);
return key;
}Sources: packages/base-ui/src/contexts/search.tsx:89-97
Warning
onKeyDown evaluates every configured hotkey item in hotKey using .every(). If v.key is a function, it receives the native KeyboardEvent; otherwise, it matches e.key === v.key. When all match, setIsOpen toggles the dialog state and e.preventDefault() is invoked.
Sources: packages/base-ui/src/contexts/search.tsx:121-126
AI Search Context and Preview Trigger Integration
The AISearch component provides a separate React context specifically for AI chat search integration, wrapping state for open, setOpen, and chat helpers initialized via @ai-sdk/react.
export function AISearch({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false);
const chat = useChat({
id: 'search',
transport: new DefaultChatTransport({
api: '/api/chat',
}),
});
return (
<Context value={useMemo(() => ({ chat, open, setOpen }), [chat, open])}>{children}</Context>
);
}Sources: packages/preview/src/components/ai/search.tsx:292-304
Trigger components such as AISearchTrigger consume this context to toggle visibility and update data-state attributes (open or closed). When position is set to float, the trigger button fixes its layout at the bottom corner with transition animations.
export function AISearchTrigger({
position = 'default',
className,
...props
}: ComponentProps<'button'> & { position?: 'default' | 'float' }) {
const { open, setOpen } = useAISearchContext();
return (
<button
data-state={open ? 'open' : 'closed'}
className={cn(
position === 'float' && [
'fixed bottom-4 gap-3 w-24 inset-e-[calc(--spacing(4)+var(--removed-body-scroll-bar-size,0px))] shadow-lg z-20 transition-[translate,opacity]',
open && 'translate-y-10 opacity-0',
],
className,
)}
onClick={() => setOpen(!open)}
{...props}
>
{props.children}
</button>
);
}Sources: packages/preview/src/components/ai/search.tsx:306-329
Related
On This Page
OverviewSearch Client React Hook ArchitectureHook API and State StructureClient Preset Resolution and DeprecationDebouncing and Task InterruptionQuery Caching via Fetch ClientDefault Route Querying via FetchFetch Client Configuration and ParametersDefault Search Dialog IntegrationOrama Cloud Client AdaptersQuery Pipeline and Index ModesOrama Search Dialog IntegrationStatic Orama and Flexsearch EnginesStatic Orama Client ImplementationStatic Flexsearch Client ImplementationAlgolia Search Client and GroupingAlgolia Lite Client AdapterHierarchical Hit Grouping and Execution FlowMixedbread Client Vector RetrievalMixedbread Options and Heading TransformationsSearch Execution and Chunk ProcessingSearch Context and Keyboard ShortcutsGlobal Search Context and Provider ArchitectureHotkey Bindings and Platform DetectionAI Search Context and Preview Trigger IntegrationRelated