Search suggestions (places, brands and categories)
getSearchSuggestions()
answers "what might the user mean?" in one call. A query like "italian" means both a cuisine
category and Ristorante Italiano down the road, and it returns both in one ranked list. A
complete query works as well as a partial one.
Basic usage
import { getSearchSuggestions } from '@tomtom-org/maps-sdk/services';
const { suggestions } = await getSearchSuggestions({ query: 'italian' });
for (const result of suggestions) {
if (result.kind === 'brand' || result.kind === 'category') {
console.log(`Refine by ${result.kind}: ${result.title}`);
} else {
console.log(result.title, result.subtitles);
}
}
Result kinds
Every result carries a kind; narrowing on it tells you what else it carries.
| Kind | What it is | Carries |
|---|---|---|
place, address, street, area |
A location | id, title, subtitles, distanceInMeters, resolve() |
brand |
A refinement: a narrowing for the next query | title, refineParams, refine() |
category |
A refinement | The same, plus category, its POICategory code |
titleis a POI's name or an address's first line;subtitlesare the remaining address lines, ending with the country only when it differs from the one the results are in.idis the place IDgetPlaceDetailstakes.
A location suggestion carries what a list needs, not the place. Call resolve() on the one the
user picks to get the full place, geometry included:
const place = await result.resolve();
map.flyTo({ center: place.geometry.coordinates });
Acting on a refinement
refine() runs the follow-up search: a brand hit searches that brand, a category hit that category,
both inside the original call's scope (its filters.countries, filters.boundingBox or geoBias).
You act on the result without knowing which endpoint produced it:
const { suggestions } = await getSearchSuggestions({ query: 'shell' });
const brand = suggestions.find((result) => result.kind === 'brand');
if (brand) {
const stations = await brand.refine();
}
refine()takes an optionalAbortSignal. The one given togetSearchSuggestionsis not reused, since that call has settled by the time a refinement is acted on.refineParamsis the fuzzy searchrefine()runs. Adjust it before sending it yourself:
const stations = await discoverPlaces({ ...brand.refineParams, limit: 50 });
Cost
- Two requests per call by default, one per arm. Narrowing
filters.kindsto refinement kinds only, or to location kinds only, skips the other arm. resolve()is for the pick, not for the whole list.- No debouncing or caching. A search box wants them per keystroke, which belongs in your input loop rather than in the service call.
Ranking
The list is blended the way Places Search v3's suggest blends it:
- At most one brand or category, and it leads. It is offered only when the whole query names it
from the start of a word:
pizzoffers Pizzeria, whilebaker standamsteroffer none. - A brand must be borne out by the places. One that merely starts like the query is dropped
unless a leading place carries it, so
londodoes not offer London Fire Brigade. - Places follow in search order. When the query names a brand or category, places whose names carry the typed words move up, since the suggestion above them already stands for the category.
- Refinement kinds alone return every recognised refinement instead of the one leader.
Neither backend returns a score comparable with the other's, so this blend is the SDK's heuristic, not the API's.
Parameters
Shaped after Places Search v3's suggest body. The full type is
SearchSuggestionsParams;
it also takes the options every service call takes.
| Parameter | Type | Description |
|---|---|---|
query |
string |
What the user typed. Required. |
limit |
number |
Size of the merged list. Default 5, capped at 10. |
filters.kinds |
SearchSuggestionKind[] |
Which kinds of result to return, and so which arms run. Default: every kind. |
filters.boundingBox |
HasBBox |
Restricts results to a box. Search arm only. geoBias then measures distances but does not rank the places. A refinement keeps the box. |
filters.countries |
string[] |
Restricts results by country. Both arms. |
filters.poiTypes |
POICategory[] |
Restricts results by POI category. Search arm only. |
filters.areaTypes |
GeographyType[] |
Restricts which administrative levels area results come from. Search arm only. |
geoBias.position |
HasLngLat |
The point to search around. Ranks nearer results higher on both arms, unless a filters.boundingBox confines the search, and is what distanceInMeters is measured from. |
geopoliticalView |
GeopoliticalView |
Defaults to the global one; see Geopolitical views. |
The autocomplete arm accepts no category filter and no bounding box, so those reach the search arm only. A
brandorcategorysuggestion is therefore not category-filtered — it is a concept, not a place.
Cancellation
Both arms share one signal, so aborting drops the pair rather than letting a slow arm resolve on
its own and repopulate the list under a newer query.
createLatestRequest
keeps only the newest keystroke's pair:
import { createLatestRequest, getSearchSuggestions } from '@tomtom-org/maps-sdk/services';
const latestSuggestions = createLatestRequest();
searchBox.addEventListener('input', async () => {
const response = await latestSuggestions.run((signal) =>
getSearchSuggestions({ query: searchBox.value, signal })
);
if (response.current) showSuggestions(response.value.suggestions);
});
A failure on either arm rejects the call rather than returning half a list, with an
SDKError whose service is 'SearchSuggestions'.
Related guides and examples
- Discover places — where a brand or category refinement's follow-up runs
- POI categories — the category vocabulary behind
categoryresults - Search playground —
getSearchSuggestions()behind a search box