Je hebt Javascript nodig om deze website te kunnen gebruiken. Pas je browserinstellingen in om verder te gaan!
Search

Advanced usage

Implement faceting, multi-index search, and geo-search with production-oriented examples.

This page builds on a configured search client and explains the patterns behind our production interfaces. Every example is also available in the code snippets repository.

Use a single index for focused search, multi-search for a global result page, and geo-search only when location is a product requirement.

Add facets to article search

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 in one request

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.

Send 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));

Process results in request order

The results array returned by Algolia corresponds directly to the order of the requests. For example:

Request IndexReturned Result
articlesresults[0]
faqsresults[1]
programsresults[2]
regional_education_desksresults[3]
podcast_episodesresults[4]
testimonialsresults[5]
videosresults[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
// }

Find programmes near a location

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.

Send a 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.

aroundLatLng
string
The center point for the search, formatted as latitude,longitude.
aroundRadius
number
Optional radius, in meters, that limits results.
getRankingInfo
boolean
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.

Query programmes and locations separately

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.

ApproachWhen to Use
Multi-searchWhen both indexes use the same search parameters
Separate queriesWhen 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.

Create 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
    };
  });

Render the 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.

Find regional education desks by area

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.

Send an 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));

Understand the area 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.

Use the geographic data structure

Each regional education desk record contains an area object describing its area of operation.

area_center
GeoPoint
Geographic centroid of the region.
area_bounds
GeoPolygon
Polygon representing the outer boundary of the region.
area_inner
GeoMultiPoint
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.

Render the matching desks

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

Keep in touch with the latest

Sign up for our monthly deep dives - straight to your inbox.