Advanced usage
Discover some of the more advanced features of the Algolia search engine with real world examples.
All examples and code snippets on this page can also be found in our code snippet repository.
Article search with faceting
Article search with faceting allows users to query the articles index and refine results using one or more filter criteria (facets). The following example illustrates a basic search and facet workflow. Implementation specifics vary by language, framework, and search client.
import { liteClient } from 'algoliasearch/lite'
import type { AlgoliaArticle } from '#schema' // Or wherever your types are defined
const ALGOLIA_APP_ID = 'WSV9PQ4NXW';
const ALGOLIA_TOKEN = 'your_algolia_token_here';
const client = liteClient(ALGOLIA_APP_ID, ALGOLIA_TOKEN)
export type AlgoliaFacetList = Record<string, Record<string, number>>
/**
* Fetch all facets for a given Algolia index.
* @param indexName - Name of the Algolia index
* @returns Complete list of facets or null if none found
*/
async function fetchAllFacetsForIndex<T>(
indexName: string,
): Promise<AlgoliaFacetList | null> {
const response = await client.searchForHits<T>({
requests: [
{
indexName,
facets: ['*'],
hitsPerPage: 0, // We don't need actual hits
maxValuesPerFacet: 5000, // We might need to adjust this based on expected facet values
},
],
})
const data = response.results[0]
if (!data || !data.facets) return null
return data.facets satisfies AlgoliaFacetList
}
const facetData = await fetchAllFacetsForIndex<AlgoliaArticle>('articles')
const facetKeys = Object.keys(facetData.value || {})
/**
* Declare a reactive state where you track selected facets
* Implementation may vary based on your framework (e.g., Vue, React, etc.)
*/
const facetState = useState('facetState', () => {
const state: Record<string, string[]> = {}
facetKeys.forEach((key) => {
state[key] = []
})
return state
})
/**
* Declare reactive state for search query with debounce
*/
const query = useState('searchQuery', () => '')
const debouncedQuery = useDebounce(query, 300)
/**
* Declare reactive state for pagination
*/
const PAGE_SIZE = 20
const MAX_PAGE_SIZE = 500
const perPage = useState('perPage', () => PAGE_SIZE)
/**
* Generate the current filter string based on selected facets
* @returns Filter string for Algolia queries
*/
const getCurrentFilter = () => {
const filters: string[] = []
facetKeys.forEach((key) => {
const selectedValues = facetState[key]
if (selectedValues.length > 0) {
filters.push(
...selectedValues.map(
(value) => `${key}:"${value}"`,
)
)
}
})
return filters.join(' AND ')
}
const response = await client.searchForHits<AlgoliaArticle>({
requests: [
{
indexName: 'articles',
query: debouncedQuery,
hitsPerPage: perPage > MAX_PAGE_SIZE ? MAX_PAGE_SIZE : perPage,
facets: ['*'],
filters: getCurrentFilter(),
// Take 50 words from bodyString for highlight snippets
attributesToSnippet: ['bodyString:50'],
},
],
})
Search multiple indexes
You don’t need to query a single index at a time. Algolia supports multi-search, allowing you to send queries to multiple indexes in a single request. This is particularly useful for implementing global search across different types of content.
Instead of performing separate API calls per index, you can send one request containing multiple index queries. The response will return results in the same order as the requested indexes, making it straightforward to map results back to their source index.
Sending the requests
The following example sends a search query to seven indexes in a single API call. Each index can define its own search configuration, while sharing the same query string.
import { algolia } from './algolia-client'
import type { SearchParams } from 'algoliasearch'
/**
* Indexes to search. The order of the indexes in this array determines
* the order of the results returned by Algolia.
*/
const INDEXES = [
'articles',
'faqs',
'programs',
'regional_education_desks',
'podcast_episodes',
'testimonials',
'videos'
] as const
type IndexName = typeof INDEXES[number]
/**
* Define search parameters per index.
* These can include attributes to retrieve, highlighting, snippets,
* and the number of hits per page.
*/
const indexSearchConfig: Record<IndexName, Partial<SearchParams>> = {
articles: {
hitsPerPage: 3,
attributesToRetrieve: [
'id',
'title',
'path',
'description',
'authors',
'topics',
'date_updated',
'date_created'
],
attributesToSnippet: ['bodyString:25'],
attributesToHighlight: ['title'],
getRankingInfo: false
},
faqs: {
hitsPerPage: 5,
attributesToRetrieve: ['id', 'question'],
attributesToSnippet: ['bodyString:20'],
attributesToHighlight: ['question', 'bodyString'],
getRankingInfo: false
},
videos: {
hitsPerPage: 3,
attributesToRetrieve: [
'id',
'title',
'path',
'video',
'duration',
'publication_date',
'profiles'
],
attributesToSnippet: ['transcript_string:50'],
attributesToHighlight: ['title'],
getRankingInfo: false
},
testimonials: {
hitsPerPage: 3,
attributesToRetrieve: [
'id',
'title',
'path',
'description',
'profiles',
'topics',
'publication_date'
],
attributesToSnippet: ['bodyString:50'],
attributesToHighlight: ['title'],
getRankingInfo: false
},
podcast_episodes: {
hitsPerPage: 3,
attributesToRetrieve: [
'id',
'title',
'path',
'show',
'duration',
'publication_date',
'hosts'
],
attributesToSnippet: ['transcript_string:50'],
attributesToHighlight: ['title'],
getRankingInfo: false
},
programs: {
hitsPerPage: 5,
attributesToRetrieve: [
'id',
'title',
'path',
'logo',
'qualifications',
'roles'
],
attributesToSnippet: [],
attributesToHighlight: ['title'],
getRankingInfo: false
},
regional_education_desks: {
hitsPerPage: 3,
attributesToRetrieve: [
'id',
'title',
'path',
'logo',
'sectors',
'regions'
],
attributesToSnippet: [],
attributesToHighlight: ['title'],
getRankingInfo: false
}
}
function getIndexSearchConfig(index: IndexName): Partial<SearchParams> {
return indexSearchConfig[index] || {}
}
/**
* Example UI state
*/
const selectedIndexes = [...INDEXES]
const query = 'onderwijs'
/**
* Execute a multi-index search request.
*/
const { results } = await algolia.searchForHits({
requests: selectedIndexes.map((index) => ({
...getIndexSearchConfig(index),
indexName: index,
query
}))
})
console.log(JSON.stringify(results, null, 2))
Processing the results
The results array returned by Algolia corresponds directly to the order of the requests. For example:
|
Request Index |
Returned Result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You can also extract the total number of hits per index, which is useful for displaying result counts in your UI.
const countByIndex = Object.fromEntries(
results.map((result, idx) => [selectedIndexes[idx], result.nbHits])
)
console.log(JSON.stringify(countByIndex, null, 2))
// Example output:
// {
// "articles": 124,
// "faqs": 38,
// "programs": 19,
// "regional_education_desks": 7,
// "podcast_episodes": 21,
// "testimonials": 11,
// "videos": 16
// }
Geo Search: Programs
Algolia supports geo-based search, allowing you to return results based on their proximity to a user’s location.
This example demonstrates how to perform a geolocation search for educational programs, while also retrieving related location data to render markers on a map.
-
A search query is executed against the
programsindex. -
Results are ranked and filtered by distance from the user's location.
-
A second query retrieves location records from the
locationsindex. -
The results are combined to produce map markers with distance information.
The example closely mirrors our own implementation at https://onderwijsloket.com/navigator/opleidingen
Example Geo Search Request
The example assumes you have access to the user’s geolocation (for example via the browser Geolocation API).
import { algolia } from './algolia-client'
/**
* Example UI State
*/
const query = 'onderwijs'
const hitsPerPage = 20
const page = 0
const userLocation = {
lat: 52.3676,
lng: 4.9041
}
const radius = 50_000 // 50 kilometers
type GeoSearchOptions = {
aroundLatLng: string
aroundRadius?: number
getRankingInfo: true
}
const geoSearchParams: GeoSearchOptions = {
aroundLatLng: [userLocation.lat, userLocation.lng].join(','),
aroundRadius: radius,
getRankingInfo: true
}
/** [lng, lat] tuple */
type Coordinates = [number, number]
interface GeoPoint {
type: 'Point'
coordinates: Coordinates
}
interface AlgoliaLocation {
id: string
objectID: string
title: string
address: string | null
zip: string | null
city: string | null
location_geopoint: GeoPoint | null
programs: {
id: string
educational_institutions: { logo: string | null; title: string }[]
}[]
}
type EducationalInstitutionType = 'hogeschool' | 'universiteit' | 'mbo'
type ProgramLevelValue =
| 'mbo'
| 'bachelor'
| 'master'
| 'phd'
| 'post-graduate'
| 'preparatory'
| 'associate'
interface AlgoliaProgram {
id: string
objectID: string
title: string
path: string
level: ProgramLevelValue | null
educational_institutions: {
id: string
title: string
path: string
logo: string | null
type: EducationalInstitutionType | null
}[]
qualifications: {
id: string
path: string
name: string
}[]
}
const locationResponse = await algolia.searchForHits<AlgoliaLocation>({
requests: [
{
indexName: 'locations',
query: '',
hitsPerPage: 500,
filters: 'has_programs:true',
attributesToRetrieve: [
'id',
'location_geopoint',
'programs',
'title',
'address',
'city',
'zip'
],
attributesToHighlight: [],
...geoSearchParams
}
]
})
const locationData = locationResponse.results[0]
const programResponse = await algolia.searchForHits<AlgoliaProgram>({
requests: [
{
indexName: 'programs',
query,
hitsPerPage,
page,
attributesToRetrieve: [
'id',
'title',
'path',
'educational_institutions',
'qualifications',
'level'
],
attributesToHighlight: [],
...geoSearchParams
}
]
})
const programData = programResponse.results[0]
const programIds = programData.hits.map(program => program.id)
Algolia provides several parameters to enable geo-based search.
|
Parameter |
Description |
|
|
The center point for the search, formatted as "latitude,longitude" |
|
|
Optional radius (in meters) to limit results |
|
|
Returns ranking metadata such as the distance from the query location |
const geoSearchParams = {
aroundLatLng: `${userLocation.lat},${userLocation.lng}`,
aroundRadius: 50000,
getRankingInfo: true
}
If aroundRadius is not provided, Algolia will rank results by proximity, but won’t enforce a strict distance limit.
Why Query Two Indexes?
This example queries both:
-
programs -
locations
The reason is data efficiency. A location can be associated with many programs. Instead of embedding full location data inside every program record, the data is stored in a separate locations index.
At the time of writing, the dataset contains roughly 200 locations for 1,000+ programs, making separate indexes more efficient.
This avoids overfetching location data when querying programs.
You could query both indexes using multi-search, but separate queries can be more efficient depending on your UI behavior.
|
Approach |
When to Use |
|
Multi-search |
When both indexes use the same search parameters |
|
Separate queries |
When filters or facets apply only to one index |
For example:
-
If facet filters only affect programs, you can re-fetch only the
programsindex when filters change. -
The
locationsquery can remain cached since it only depends on user location and radius.
Creating Map Markers
Once both responses are retrieved, you can construct map markers from the location results. The code below filters locations to only include those associated with the retrieved programs.
const mapMarkers = locationData.hits
.filter(
location =>
location.programs
.map(program => program.id)
.some(id => programIds.includes(id)) &&
!!location.location_geopoint?.coordinates
)
.map(location => {
const addressLine = location.address
? `${location.address}, ${location.city} ${location.zip}`
: location.city
? location.city
: undefined
return {
id: location.id,
title: location.title,
collection: 'programs',
coordinates: location.location_geopoint!.coordinates,
description: addressLine,
programsCount: location.programs.length,
distance: location._rankingInfo!.geoDistance
}
})
Result
The final mapMarkers array can be used to render interactive map markers in your UI, while the programs results populate a search results list sorted by proximity.
Geo Search: Regional Education Desks
Unlike entities such as programs, educational institutions, or locations, regional education desks are not tied to a single geographic point.
Instead, each desk operates within a defined geographic region.
To support geo-based search in Algolia, each record stores:
-
A coordinate grid representing the area of operation
-
Detailed geographic information describing the region boundaries
This allows Algolia to calculate distances between the user’s location and points within the region, making it possible to retrieve desks relevant to the user’s search area.
The example closely mirrors our own implementation at https://onderwijsloket.com/navigator/regioloketten
Example Area Search Request
import { algolia } from './algolia-client'
import { GeoPoint, GeoPolygon, GeoMultiPoint, GeoSearchOptions } from './types/geolocation'
/**
* Example UI State
*/
const query = ''
const hitsPerPage = 20
const page = 0
const userLocation = {
lat: 52.3676,
lng: 4.9041
}
const radius = 50_000 // 50 kilometers
/**
* Construct the geo search params object to use
*/
const geoSearchParams: GeoSearchOptions = {
aroundLatLng: [userLocation.lat, userLocation.lng].join(','),
aroundRadius: radius,
getRankingInfo: true
}
/**
* Interface for the Regional Education Desks record we will be fetching
*/
interface AlgoliaRegionalEducationDesk {
id: string
objectID: string
title: string
sectors: {
id: string
title: string
slug: string
}[]
logo: string | null
email: string | null
phone: string | null
path: string
regions: string[]
area: {
id: string
title: string | null
area_center: GeoPoint
area_bounds: GeoPolygon
area_inner: GeoMultiPoint
} | null
}
const regionalEducationDesksResponse =
await algolia.searchForHits<AlgoliaRegionalEducationDesk>({
requests: [
{
indexName: 'regional_education_desks',
query,
hitsPerPage,
page,
attributesToRetrieve: [
'title',
'regions',
'sectors',
'email',
'logo',
'phone',
'path',
'area'
],
attributesToHighlight: [],
...geoSearchParams
}
]
})
const data = regionalEducationDesksResponse.results[0]
console.log(JSON.stringify(data, null, 2))
Why This Requires a Workaround
Algolia’s geo-search features are designed primarily for point-based locations. However, in this case each record represents a geographic region (polygon) rather than a single coordinate.
Algolia does not natively support:
-
Checking if two polygons intersect
-
Calculating the distance between a polygon and a point
To work around this limitation, each regional education desk record includes a grid of coordinates representing the region. These coordinates are stored in the _geoloc property, which Algolia automatically uses for geo-based searches.
By indexing a grid of coordinates within the region, Algolia can calculate distances between the user’s location and multiple points inside the region, effectively approximating area intersection queries.
Geographic Data Structure
Each regional education desk record contains an area object describing its area of operation.
|
Property |
Description |
|
|
Geographic centroid of the region |
|
|
Polygon representing the outer boundary of the region |
|
|
Coordinate grid covering the region |
The coordinate grid (area_inner) is used for geo-search, while the polygon (area_bounds) can be used for visualization or map rendering.
Using the Results
The returned records contain both metadata about the desk and geographic information describing its service region.
This allows you to:
-
Display regional desks near the user
-
Show service areas on a map
-
Provide contact information for the relevant desk
Typical UI elements include:
-
Map markers positioned at
area_center -
Highlighted polygons using
area_bounds -
Lists of nearby desks sorted by distance