Documentation Index Fetch the complete documentation index at: https://mintlify.com/algolia/docsearch/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows you how to build custom DocSearch integrations using the core packages. Whether you’re building for a new framework, a custom documentation platform, or need advanced customization, this guide covers the fundamentals.
Understanding DocSearch Packages
DocSearch is built as a modular system with several packages:
@docsearch/js Vanilla JavaScript wrapper using Preact
@docsearch/react React components and hooks
@docsearch/core Framework-agnostic core logic
@docsearch/css Styles and CSS variables
Architecture Overview
DocSearch is built on top of Algolia’s Autocomplete library:
Building a Framework Integration
Approach 1: Wrap the JavaScript Package
The simplest approach is to wrap @docsearch/js:
Install dependencies
npm install @docsearch/js @docsearch/css
Create a wrapper component
Example for Vue 3: < script setup >
import { onMounted , onUnmounted , ref } from 'vue'
import docsearch from '@docsearch/js'
import '@docsearch/css'
const props = defineProps ({
appId: { type: String , required: true },
apiKey: { type: String , required: true },
indexName: { type: String , required: true },
placeholder: { type: String , default: 'Search docs' },
searchParameters: { type: Object , default : () => ({}) },
})
const instance = ref ( null )
onMounted (() => {
instance . value = docsearch ({
container: '#docsearch' ,
appId: props . appId ,
apiKey: props . apiKey ,
indexName: props . indexName ,
placeholder: props . placeholder ,
searchParameters: props . searchParameters ,
})
})
onUnmounted (() => {
instance . value ?. destroy ()
})
// Expose methods
defineExpose ({
open : () => instance . value ?. open (),
close : () => instance . value ?. close (),
})
</ script >
< template >
< div id = "docsearch" ></ div >
</ template >
Use the component
< script setup >
import DocSearch from './components/DocSearch.vue'
</ script >
< template >
< div >
< header >
< h1 > My Docs </ h1 >
< DocSearch
app-id = "YOUR_APP_ID"
api-key = "YOUR_SEARCH_API_KEY"
index-name = "YOUR_INDEX_NAME"
/>
</ header >
</ div >
</ template >
Approach 2: Use React Components Directly
Many frameworks support React components. Example for Solid.js:
import { createSignal , onMount , onCleanup } from 'solid-js'
import { DocSearch as DocSearchReact } from '@docsearch/react'
import '@docsearch/css'
export function DocSearch ( props ) {
return (
< DocSearchReact
appId = { props . appId }
apiKey = { props . apiKey }
indexName = { props . indexName }
/>
)
}
Approach 3: Build from Core
For complete control, use @docsearch/core directly:
import { useDocSearch , DocSearchProvider } from '@docsearch/core'
import type { DocSearchProps } from '@docsearch/core'
// Create your own UI using the core hooks
export function createDocSearch ( props : DocSearchProps ) {
// Use core logic
const {
query ,
collections ,
isOpen ,
recentSearches ,
favoriteSearches ,
openModal ,
closeModal ,
// ... other state and methods
} = useDocSearch ()
// Build your custom UI with this data
return {
query ,
collections ,
isOpen ,
open: openModal ,
close: closeModal ,
// Expose what you need
}
}
Custom Search Client
You can customize the Algolia search client:
import docsearch from '@docsearch/js'
import { liteClient as algoliasearch } from 'algoliasearch/lite'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
transformSearchClient : ( searchClient ) => {
// Add custom Algolia agent
searchClient . addAlgoliaAgent ( 'my-integration' , '1.0.0' )
// Intercept search requests
const originalSearch = searchClient . search
searchClient . search = async ( requests ) => {
console . log ( 'Searching:' , requests )
const results = await originalSearch ( requests )
console . log ( 'Results:' , results )
return results
}
return searchClient
},
})
Transform search results before rendering:
import docsearch from '@docsearch/js'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
transformItems : ( items ) => {
return items . map (( item ) => {
// Transform URLs
if ( item . url . startsWith ( '/' )) {
item . url = `https://docs.example.com ${ item . url } `
}
// Add custom metadata
item . metadata = {
lastUpdated: item . _highlightResult ?. lastModified ?. value ,
}
// Filter out certain results
if ( item . hierarchy . lvl0 === 'Internal' ) {
return null
}
return item
}). filter ( Boolean )
},
})
Custom Navigation
Control how DocSearch navigates:
import docsearch from '@docsearch/js'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
navigator: {
navigate ({ itemUrl }) {
// Custom navigation logic
if ( itemUrl . startsWith ( 'http' )) {
// External link - open in new tab
window . open ( itemUrl , '_blank' )
} else {
// Internal link - use your router
yourRouter . push ( itemUrl )
}
},
navigateNewTab ({ itemUrl }) {
window . open ( itemUrl , '_blank' )
},
navigateNewWindow ({ itemUrl }) {
window . open ( itemUrl , '_blank' , 'noopener,noreferrer' )
},
},
})
Custom Hit Component
Customize how individual results are rendered:
import docsearch from '@docsearch/js'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
hitComponent : ({ hit , children }, { html }) => {
// Get the result type from hierarchy
const type = hit . type || 'content'
const icon = {
content: '📄' ,
lvl1: '📂' ,
lvl2: '📋' ,
}[ type ] || '📄'
return html `
<a href=" ${ hit . url } " class="custom-hit" data-type=" ${ type } ">
<span class="hit-icon"> ${ icon } </span>
<div class="hit-content">
<div class="hit-title">
${ children }
</div>
${ hit . hierarchy . lvl0 && html `
<div class="hit-path">
${ hit . hierarchy . lvl0 }
</div>
` }
</div>
</a>
`
},
})
Add a custom footer to the results:
import docsearch from '@docsearch/js'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
resultsFooterComponent : ({ state }, { html }) => {
const nbHits = state . context ?. nbHits || 0
const query = state . query
if ( ! query ) return null
return html `
<div class="custom-footer">
<div class="results-count">
Found ${ nbHits } results for " ${ query } "
</div>
<div class="footer-actions">
<a href="/search?q= ${ encodeURIComponent ( query ) } " class="view-all">
View all results
</a>
<a href="/feedback?q= ${ encodeURIComponent ( query ) } " class="feedback">
Results not relevant?
</a>
</div>
</div>
`
},
})
TypeScript Integration
Create type-safe wrappers:
import docsearch from '@docsearch/js'
import type { DocSearchProps , DocSearchInstance } from '@docsearch/js'
import '@docsearch/css'
export interface CustomDocSearchProps extends Omit < DocSearchProps , 'container' > {
// Add custom props
onSearch ?: ( query : string ) => void
onSelect ?: ( item : any ) => void
analytics ?: {
trackSearch : ( query : string ) => void
trackClick : ( item : any ) => void
}
}
export function createDocSearch (
container : string | HTMLElement ,
props : CustomDocSearchProps
) : DocSearchInstance {
const { onSearch , onSelect , analytics , ... docSearchProps } = props
// Create custom transformSearchClient
const originalTransform = props . transformSearchClient
const transformSearchClient = ( searchClient : any ) => {
let client = searchClient
// Apply original transform first
if ( originalTransform ) {
client = originalTransform ( client )
}
// Add analytics
if ( analytics ) {
const originalSearch = client . search
client . search = async ( requests : any []) => {
const query = requests [ 0 ]?. params ?. query
if ( query ) {
analytics . trackSearch ( query )
}
return originalSearch ( requests )
}
}
return client
}
// Create instance with enhanced props
const instance = docsearch ({
container ,
... docSearchProps ,
transformSearchClient ,
transformItems : ( items ) => {
// Call original transform if provided
const transformed = props . transformItems ?.( items ) ?? items
// Add click tracking
if ( analytics ) {
return transformed . map (( item ) => ({
... item ,
onClick : () => {
analytics . trackClick ( item )
onSelect ?.( item )
},
}))
}
return transformed
},
})
return instance
}
Usage:
const instance = createDocSearch ( '#docsearch' , {
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
analytics: {
trackSearch : ( query ) => {
console . log ( 'Search:' , query )
},
trackClick : ( item ) => {
console . log ( 'Click:' , item )
},
},
})
Server-Side Rendering
Handle SSR in your custom integration:
import { useEffect , useRef } from 'react'
import type { DocSearchProps } from '@docsearch/js'
export function DocSearchSSR ( props : Omit < DocSearchProps , 'container' >) {
const containerRef = useRef < HTMLDivElement >( null )
const instanceRef = useRef < any >( null )
useEffect (() => {
// Only run on client
if ( typeof window === 'undefined' ) return
// Dynamically import to avoid SSR issues
import ( '@docsearch/js' ). then (({ default : docsearch }) => {
if ( containerRef . current && ! instanceRef . current ) {
instanceRef . current = docsearch ({
container: containerRef . current ,
... props ,
})
}
})
// Cleanup
return () => {
instanceRef . current ?. destroy ()
instanceRef . current = null
}
}, [ props ])
return < div ref = { containerRef } />
}
Analytics Integration
Track search usage:
import docsearch from '@docsearch/js'
import { track } from './analytics'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
onOpen : () => {
track ( 'search_opened' )
},
onClose : () => {
track ( 'search_closed' )
},
transformSearchClient : ( searchClient ) => {
const originalSearch = searchClient . search
searchClient . search = async ( requests ) => {
const query = requests [ 0 ]?. params ?. query
if ( query ) {
track ( 'search_query' , { query , timestamp: Date . now () })
}
const results = await originalSearch ( requests )
const hits = results . results [ 0 ]?. hits ?. length || 0
track ( 'search_results' , { query , hits })
return results
}
return searchClient
},
navigator: {
navigate ({ itemUrl , item }) {
track ( 'search_result_clicked' , {
url: itemUrl ,
title: item . hierarchy ?. lvl1 || item . hierarchy ?. lvl0 ,
})
window . location . href = itemUrl
},
},
})
A/B Testing
Implement A/B testing for search:
import docsearch from '@docsearch/js'
// Determine variant
const variant = Math . random () < 0.5 ? 'A' : 'B'
const instance = docsearch ({
container: '#docsearch' ,
appId: 'YOUR_APP_ID' ,
apiKey: 'YOUR_SEARCH_API_KEY' ,
indexName: 'YOUR_INDEX_NAME' ,
searchParameters: {
// Variant A: default behavior
// Variant B: boost recent content
... ( variant === 'B' && {
optionalFilters: [ 'recentContent:true<score=2>' ],
}),
},
transformSearchClient : ( searchClient ) => {
// Add variant to user agent
searchClient . addAlgoliaAgent ( 'ab-test' , variant )
return searchClient
},
})
// Track which variant performed better
window . addEventListener ( 'beforeunload' , () => {
analytics . track ( 'ab_variant' , { variant , searches: searchCount })
})
Testing Your Integration
Write tests for your custom integration:
import { describe , it , expect , vi } from 'vitest'
import { createDocSearch } from './docsearch-wrapper'
describe ( 'DocSearch Custom Integration' , () => {
it ( 'should initialize with correct props' , () => {
const container = document . createElement ( 'div' )
container . id = 'docsearch'
document . body . appendChild ( container )
const instance = createDocSearch ( '#docsearch' , {
appId: 'test-app-id' ,
apiKey: 'test-api-key' ,
indexName: 'test-index' ,
})
expect ( instance ). toBeDefined ()
expect ( instance . isReady ). toBe ( true )
})
it ( 'should call analytics on search' , async () => {
const trackSearch = vi . fn ()
const instance = createDocSearch ( '#docsearch' , {
appId: 'test-app-id' ,
apiKey: 'test-api-key' ,
indexName: 'test-index' ,
analytics: {
trackSearch ,
trackClick: vi . fn (),
},
})
// Simulate search
instance . open ()
// ... trigger search
expect ( trackSearch ). toHaveBeenCalled ()
})
})
Best Practices
1. Handle Lifecycle Properly
Always clean up DocSearch instances when components unmount: onUnmounted (() => {
instance . value ?. destroy ()
})
2. Optimize for Performance
Use TypeScript for better developer experience: import type { DocSearchProps , DocSearchHit } from '@docsearch/js'
Handle errors gracefully: transformSearchClient : ( searchClient ) => {
const originalSearch = searchClient . search
searchClient . search = async ( requests ) => {
try {
return await originalSearch ( requests )
} catch ( error ) {
console . error ( 'Search failed:' , error )
// Show user-friendly error
return { results: [] }
}
}
return searchClient
}
Ensure your custom integration maintains accessibility:
Keep keyboard navigation working
Preserve ARIA attributes
Test with screen readers
Examples by Framework
Vue 3 See the Vue 3 example above for wrapping @docsearch/js
Svelte Similar to Vue, use onMount and onDestroy lifecycle hooks
Angular Create a directive or component that initializes DocSearch in ngAfterViewInit
Next.js Use dynamic imports to avoid SSR issues
Next Steps
API Reference Complete API documentation
Styling Customize appearance
Core Package Use @docsearch/core directly
Contributing Contribute your integration