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

articles

results[0]

faqs

results[1]

programs

results[2]

regional_education_desks

results[3]

podcast_episodes

results[4]

testimonials

results[5]

videos

results[6]

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 programs index.

  • Results are ranked and filtered by distance from the user's location.

  • A second query retrieves location records from the locations index.

  • 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

aroundLatLng

The center point for the search, formatted as "latitude,longitude"

aroundRadius

Optional radius (in meters) to limit results

getRankingInfo

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 programs index when filters change.

  • The locations query 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

area_center

Geographic centroid of the region

area_bounds

Polygon representing the outer boundary of the region

area_inner

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