Your data

Listings and places

Records and settings for real estate, rentals, jobs, directories, and store locators: one location per record, and searches near a point, within a radius, or inside a map.

People look for listings and places near them or in an area, then narrow by price, size, or type. Give each record a location, and searches can find it near a point, within a radius, or inside what a map shows. The map playground searches 35,000 restaurants this way.

Records

One record per listing or place, with its location:

{
  "objectID": "listing-48213",
  "title": "Bright two-bedroom flat with a balcony",
  "type": "Apartment",
  "area": "Reykjavík 105",
  "address": "Laugavegur 120",
  "description": "A sunny corner flat on the third floor…",
  "price": 2150,
  "bedrooms": 2,
  "floorArea": 74,
  "petsAllowed": true,
  "listedAt": 1790812800,
  "_geoloc": { "lat": 64.1432, "lng": -21.9126 },
  "image": "https://cdn.example.com/listing-48213.webp",
  "url": "/rent/listing-48213"
}
  • _geoloc holds lat and lng numbers. Make it the index's location field. A record has one location, so a business with several sites gets one record per site, as in a store locator.
  • What people tick, such as type, area, and bedrooms, are facets. Ranges, such as price and floor area, are numbers to filter by.
  • listedAt sorts newest first.
  • Closed listings leave search. Delete a listing when it's sold, rented, or filled, so it doesn't show up or count in facets. Keep it with a status field only if you show closed listings on purpose.

Settings

{
  searchableAttributes: [
    { field: "title", weight: 8 },
    { field: "area", weight: 6 },
    { field: "address", weight: 4 },
    { field: "type", weight: 3 },
    { field: "description", weight: 1 },
  ],
  facetFields: ["type", "area", "bedrooms", "petsAllowed"],
  filterFields: ["price", "floorArea"],
  sorts: [
    { id: "relevance", label: "Relevance", field: "relevance", direction: "desc", thenBy: [] },
    { id: "price-asc", label: "Price: low to high", field: "price", direction: "asc", thenBy: [] },
    { id: "price-desc", label: "Price: high to low", field: "price", direction: "desc", thenBy: [] },
    { id: "newest", label: "Newest", field: "listedAt", direction: "desc", thenBy: [] },
  ],
  defaultSort: "relevance",
  locationField: "_geoloc",
  didYouMean: true,
  languages: ["en"],
}

distance is a built-in sort, so "Nearest first" needs no setting.

Near the visitor, within 5 km of their city:

const results = await client.search({
  near: { visitor: true, radius: 5000 },
  filters: { bedrooms: { gte: 2 }, price: { lte: 2500 } },
  facets: ["type", "area"],
})
results.hits[0].distance // meters from the visitor's city center

Inside a map, nearest its center first:

const results = await client.search({
  q: "balcony",
  within: { north: 64.16, south: 64.12, east: -21.85, west: -21.98 },
  near: { lat: 64.14, lng: -21.915 },
  facets: ["type", "bedrooms"],
})
  • With a query, relevance comes first, and distance orders the matches that are about equally good. Without one, results are nearest first. See Location.
  • Maps: use any map library, and search again when the map moves. In React, useGeoSearch does this.
  • Exact positions, such as a phone's GPS, rarely repeat, so their searches miss the cache and count toward your plan. Round coordinates to three decimals (about 100 m) so nearby searches share results.

Store locators

For a chain's stores, a record is a store: its name, address, city, opening hours, and services as true or false, such as driveThrough. Search near the visitor with a radius, and offer the services as facets. The map playground is a store locator for 35,000 restaurants.