Advanced usage
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.
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 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
// }
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.
Send a geo-search request
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.
latitude,longitude.const geoSearchParams = {
aroundLatLng: `${userLocation.lat},${userLocation.lng}`,
aroundRadius: 50000,
getRankingInfo: true
};
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:
programslocations
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.
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.
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.
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.
Use the geographic data structure
Each regional education desk record contains an area object describing its area of operation.
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.