> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# geoSearch

> Lets users perform location-based searches inside a bounding box on a map.

```ts Signature theme={"system"}
geoSearch({
  container: string | HTMLElement,
  googleReference: object,
  // Optional parameters
  initialZoom?: number,
  initialPosition?: object,
  mapOptions?: object,
  builtInMarker?: object,
  customHTMLMarker?: object,
  enableRefine?: boolean,
  enableClearMapRefinement?: boolean,
  enableRefineControl?: boolean,
  enableRefineOnMapMove?: boolean,
  templates?: object,
  cssClasses?: object,
});
```

## Import

<CodeGroup>
  ```js Package manager theme={"system"}
  import { geoSearch } from 'instantsearch.js/es/widgets';
  ```

  ```js CDN theme={"system"}
  const { geoSearch } = instantsearch.widgets;
  // or directly use instantsearch.widgets.geoSearch()
  ```
</CodeGroup>

<Card title="See this widget in action" icon="monitor-play" href="https://instantsearchjs.netlify.app/stories/js/?path=/story/results-geosearch--default" horizontal>
  Preview this widget and its behavior.
</Card>

## About this widget

The `geoSearch` widget lets you search for results based on their position within a specified area (a bounding box).
It also provides features such as "search on map interactions".

<Note>
  The `geoSearch` widget doesn't let you [search around a central point](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-around-a-location)
  or [within polygons](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-polygonal-area).
  If you want this, you need to build your own UI on top of the Algolia API.
</Note>

### Requirements

* Your hit records must have a [`_geoloc` attribute](/doc/guides/managing-results/refine-results/geolocation#enabling-geo-search-by-adding-geolocation-data-to-records) so they can be displayed on the map.
* You must load the [Google Maps library](https://developers.google.com/maps/documentation/javascript/overview) and pass a reference to the widget: the library doesn't come with InstantSearch.js.
* You must explicitly set the map container's `height`. For example:

```css CSS theme={"system"}
.ais-GeoSearch-map {
  height: 500px; // You can change this height 
}
```

## Examples

```js JavaScript icon=code theme={"system"}
geoSearch({
  container: "#geo-search",
  googleReference: window.google,
});
```

## Options

<ParamField body="container" type="string | HTMLElement" required>
  The CSS Selector or `HTMLElement` to insert the widget into.

  <CodeGroup>
    ```js string theme={"system"}
    geoSearch({
      // ...
      container: "#geo-search",
    });
    ```

    ```js HTMLElement theme={"system"}
    geoSearch({
      // ...
      container: document.querySelector("#geo-search"),
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="googleReference" type="object" required>
  The reference to the global `window.google` object.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    googleReference: window.google,
  });
  ```
</ParamField>

<ParamField body="initialZoom" type="number" default={1}>
  By default, the map sets the zoom level based on the displayed markers (results).
  However, after InstantSearch has applied a refinement, there may be no results.
  When this happens, a specific zoom level is required to render the map.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    initialZoom: 4,
  });
  ```
</ParamField>

<ParamField body="initialPosition" type="object" default="{lat: 0, lng: 0}">
  By default, the map sets the position based on the displayed markers (results).
  However, after InstantSearch has applied a refinement, there may be no results.
  When this happens, a specific position is required to render the map.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    initialPosition: {
      lat: 48.864716,
      lng: 2.349014,
    },
  });
  ```
</ParamField>

<ParamField body="mapOptions" type="object">
  The [options forwarded to the Google Maps constructor](https://developers.google.com/maps/documentation/javascript/reference#MapOptions).

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    mapOptions: {
      streetViewControl: true,
    },
  });
  ```
</ParamField>

<ParamField body="builtInMarker" type="object">
  The options for customizing the built-in Google Maps markers.
  This is ignored when the [`customHTMLMarker`](#param-custom-html-marker) is provided.
  The object accepts multiple attributes:

  * `createOptions: function`. A function to create the [options passed to the Google Maps marker](https://developers.google.com/maps/documentation/javascript/reference#MapOptions). The function is called with `item`, which is the `hit` tied to the marker.
  * `events: object`. An object that takes event types (such as `click` and `mouseover`) as keys and listeners as values. The listener is called with an object that contains properties `event`, `item`, `marker`, and `map`.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    builtInMarker: {
      createOptions(item) {
        return {
          title: item.name,
        };
      },
      events: {
        click({ event, item, marker, map }) {
          console.log(item);
        },
      },
    },
  });
  ```
</ParamField>

<ParamField body="customHTMLMarker" type="object">
  The options for customizing the HTML marker.
  InstantSearch.js provides an alternative to the built-in Google Maps markers.
  You can use plain HTML to build your custom markers (see [`templates.HTMLMarker`](/doc/api-reference/widgets/geo-search/js#param-html-marker)).
  The `customHTMLMarker` object accepts several attributes:

  * `createOptions: function`. A function to create the options passed to the `HTMLMarker`. The only supported option is `anchor`. It lets you shift the marker position from the center of the element.
  * `events: object`. An object that takes event types (such as `click` and `mouseover`) as keys and listeners as values. The listener is called with an object that contains properties `event`, `item`, `marker`, and `map`.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    customHTMLMarker: {
      createOptions(item) {
        return {
          anchor: {
            x: 0,
            y: 0,
          },
        };
      },
      events: {
        click({ event, item, marker, map }) {
          console.log(item);
        },
      },
    },
  });
  ```
</ParamField>

<ParamField body="enableRefine" type="boolean" default={true}>
  If `true`, the map is used for refining the search.
  Otherwise, it's only for display purposes.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    enableRefine: false,
  });
  ```
</ParamField>

<ParamField body="enableClearMapRefinement" type="boolean" default={true}>
  If `true`, a button is displayed on the map to allow users to clear any refinements.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    enableClearMapRefinement: false,
  });
  ```
</ParamField>

<ParamField body="enableRefineControl" type="boolean" default={true}>
  If `true`, users can toggle the option [`enableRefineOnMapMove`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-on-map-move) directly from the map.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    enableRefineControl: false,
  });
  ```
</ParamField>

<ParamField body="enableRefineOnMapMove" type="boolean" default={true}>
  If `true`, refine is triggered as you move the map.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    enableRefineOnMapMove: false,
  });
  ```
</ParamField>

<ParamField body="templates" type="object">
  The [templates](#templates) to use for the widget.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    templates: {
      // ...
    },
  });
  ```
</ParamField>

<ParamField body="cssClasses" type="object" default="{}">
  The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets):

  * `root`. The widget's root element.
  * `map`. The map element.
  * `control`. The control element.
  * `label`. The label for the control element.
  * `selectedLabel`. The control element's selected label.
  * `input`. The control element's input.
  * `redo`. The "Redo search" button.
  * `disabledRedo`. The disabled "Redo search" button.
  * `reset`. The "Reset refinement" button.

  ```js JavaScript icon=code theme={"system"}
  geoSearch({
    // ...
    cssClasses: {
      root: "MyCustomGeoSearch",
      map: ["MyCustomGeoSearchMap", "MyCustomGeoSearchMap--subclass"],
    },
  });
  ```
</ParamField>

## Templates

You can customize parts of a widget’s UI using the Templates API.

Each template includes an `html` function,
which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates).
This function safely renders templates as HTML strings and works directly in the browser—no build step required.
For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js/#templating-your-ui).

<Note>
  The `html` function is available in InstantSearch.js version 4.46.0 or later.
</Note>

<ParamField body="HTMLMarker" type="string | function">
  The template to use for the marker.

  <CodeGroup>
    ```js function theme={"system"}
    geoSearch({
      // ...
      templates: {
        HTMLMarker(_, { html }) {
          return html`<p>Your custom HTML Marker</p>`;
        },
      },
    });
    ```

    ```js string theme={"system"}
    // String-based templates are deprecated, use functions instead.
    geoSearch({
      // ...
      templates: {
        HTMLMarker: "<p>Your custom HTML Marker</p>",
      },
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="reset" type="string | function">
  The template for the reset button.

  <CodeGroup>
    ```js function theme={"system"}
    geoSearch({
      // ...
      templates: {
        reset(_, { html }) {
          return html`<strong>Clear the map refinement</strong>`;
        },
      },
    });
    ```

    ```js string theme={"system"}
    // String-based templates are deprecated, use functions instead.
    geoSearch({
      // ...
      templates: {
        reset: "Clear the map refinement",
      },
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="toggle" type="string | function">
  The template for the toggle label.

  <CodeGroup>
    ```js function theme={"system"}
    geoSearch({
      // ...
      templates: {
        toggle(_, { html }) {
          return html`<strong>Search as I move the map</strong>`;
        },
      },
    });
    ```

    ```js string theme={"system"}
    // String-based templates are deprecated, use functions instead.
    geoSearch({
      // ...
      templates: {
        toggle: "Search as I move the map",
      },
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="redo" type="string | function">
  The template for the redo label.

  <CodeGroup>
    ```js function theme={"system"}
    geoSearch({
      // ...
      templates: {
        redo(_, { html }) {
          return html`<strong>Redo search here</strong>`;
        },
      },
    });
    ```

    ```js string theme={"system"}
    // String-based templates are deprecated, use functions instead.
    geoSearch({
      // ...
      templates: {
        redo: "Redo search here",
      },
    });
    ```
  </CodeGroup>
</ParamField>

## HTML output

```html HTML icon=code-xml theme={"system"}
<div class="ais-GeoSearch">
  <div class="ais-GeoSearch-map">
    <!-- Map element here -->
  </div>
  <div class="ais-GeoSearch-control">
    <label class="ais-GeoSearch-label">
      <input class="ais-GeoSearch-input" type="checkbox" />
      Search as I move the map
    </label>
  </div>
  <button class="ais-GeoSearch-reset">Clear the map refinement</button>
</div>
```

## Customize the UI with `connectGeoSearch`

If you want to create your own UI of the `geoSearch` widget, you can use connectors.

To use `connectGeoSearch`, you can import it with the declaration relevant to how you installed InstantSearch.js.

<CodeGroup>
  ```js Package manager theme={"system"}
  import { connectGeoSearch } from 'instantsearch.js/es/connectors';
  ```

  ```js CDN theme={"system"}
  const { connectGeoSearch } = instantsearch.connectors;
  // or directly use instantsearch.connectors.connectGeoSearch()
  ```
</CodeGroup>

Then it's a 3-step process:

```js JavaScript icon=code theme={"system"}
// 1. Create a render function
const renderGeoSearch = (renderOptions, isFirstRender) => {
  // Rendering logic
};

// 2. Create the custom widget
const customGeoSearch = connectGeoSearch(renderGeoSearch);

// 3. Instantiate
search.addWidgets([
  customGeoSearch({
    // instance params
  }),
]);
```

### Create a render function

This rendering function is called before the first search (`init` lifecycle step)
and each time results come back from Algolia (`render` lifecycle step).

```js JavaScript icon=code theme={"system"}
const renderGeoSearch = (renderOptions, isFirstRender) => {
  const {
    items,
    position,
    currentRefinement,
    refine,
    sendEvent,
    clearMapRefinement,
    isRefinedWithMap,
    toggleRefineOnMapMove,
    isRefineOnMapMove,
    setMapMoveSinceLastRefine,
    hasMapMoveSinceLastRefine,
    widgetParams,
  } = renderOptions;

  if (isFirstRender) {
    // Do some initial rendering and bind events
  }

  // Render the widget
};
```

#### Rendering options

The following rendering option example code snippets use [Leaflet](https://leafletjs.com/examples/quick-start) to render the map.
If you prefer not to use Leaflet, you can use another library (such as [Google Maps](https://developers.google.com/maps/documentation/javascript/overview) or [Mapbox](https://www.mapbox.com)).

<ParamField body="items" type="object[]">
  Hits that matched the search request.

  ```js JavaScript icon=code theme={"system"}
  let 
  let markers = [];

  const renderGeoSearch = (renderOptions, isFirstRender) => {
    const { items } = renderOptions;

    if (isFirstRender) {
      map = L.map(document.querySelector("#geo-search"));

      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
      }).addTo(map);
    }

    markers.forEach((marker) => marker.remove());

    markers = items.map(({ _geoloc }) =>
      L.marker([_geoloc.lat, _geoloc.lng]).addTo(map),
    );

    if (markers.length) {
      map.fitBounds(L.featureGroup(markers).getBounds());
    }
  };
  ```
</ParamField>

<ParamField body="position" type="object">
  The current search position, when applicable.

  ```js JavaScript icon=code theme={"system"}
  let map = null;
  let markers = [];

  const renderGeoSearch = (renderOptions, isFirstRender) => {
    const { items, position } = renderOptions;

    if (isFirstRender) {
      map = L.map(document.querySelector("#geo-search"));

      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
      }).addTo(map);
    }

    markers.forEach((marker) => marker.remove());

    markers = items.map(({ _geoloc }) =>
      L.marker([_geoloc.lat, _geoloc.lng]).addTo(map),
    );

    if (markers.length) {
      map.fitBounds(L.featureGroup(markers).getBounds());
    } else {
      map.setView(
        position || {
          lat: 48.864716,
          lng: 2.349014,
        },
        12,
      );
    }
  };
  ```
</ParamField>

<ParamField body="currentRefinement" type="object">
  The search's bounding box:

  * `northEast: { lat: number, lng: number }`. The top right corner of the map view.
  * `southWest: { lat: number, lng: number }`. The bottom left corner of the map view.
</ParamField>

<ParamField body="refine" type="function">
  Sets a bounding box to filter the results from the given map bounds. The function accepts an object with:

  * `northEast: { lat: number, lng: number }`. The top right corner of the map view.
  * `southWest: { lat: number, lng: number }`. The bottom left corner of the map view.
</ParamField>

<ParamField body="sendEvent" type="(eventType, hit, eventName) => void">
  The function to send `click` or `conversion` events.
  The `view` event is automatically sent when this connector renders hits.
  To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware.

  * `eventType: 'click' | 'conversion'`
  * `hit: Hit | Hit[]`
  * `eventName: string`

  ```js JavaScript icon=code theme={"system"}
  // For example,
  sendEvent("click", hit, "Location Starred");
  // or
  sendEvent("conversion", hit, "Restaurant Saved");

  /*
    A payload like the following will be sent to the `insights` middleware.
    {
      eventType: 'click',
      insightsMethod: 'clickedObjectIDsAfterSearch',
      payload: {
        eventName: 'Product Added',
        index: '<index-name>',
        objectIDs: ['<object-id>'],
        positions: [<position>],
        queryID: '<query-id>',
      },
      widgetType: 'ais.geoSearch',
    }
  */
  ```
</ParamField>

<ParamField body="clearMapRefinement" type="function">
  Resets the current bounding box refinement.
</ParamField>

<ParamField body="isRefinedWithMap" type="function">
  Returns `true` if the current refinement is set with the map bounds.
</ParamField>

<ParamField body="toggleRefineOnMapMove" type="function">
  Toggles whether users can refine on map move.
</ParamField>

<ParamField body="isRefineOnMapMove" type="function">
  Returns `true` if users can refine on map move.
</ParamField>

<ParamField body="setMapMoveSinceLastRefine" type="function">
  Indicates that the map has moved since the last refinement.
  This function should be called on each map move.
  It helps ensure the map is only re-rendered when it has moved.
</ParamField>

<ParamField body="hasMapMoveSinceLastRefine" type="function">
  Returns `true` if the map has moved since the last refinement.
</ParamField>

<ParamField body="widgetParams" type="object">
  All original widget options forwarded to the render function.
</ParamField>

### Create and instantiate the custom widget

First, create your custom widgets using a rendering function.
Then, instantiate them with parameters.

There are two kinds of parameters you can pass:

* **Instance parameters**. Predefined options that configure Algolia's behavior.
* **Custom parameters**. Parameters you define to make the widget reusable and adaptable.

Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`.

```js JavaScript icon=code theme={"system"}
const customGeoSearch = connectGeoSearch(renderGeoSearch);

search.addWidgets([
  customGeoSearch({
    // Optional parameters
    enableRefineOnMapMove,
    transformItems,
  }),
]);
```

#### Instance options

<ParamField body="enableRefineOnMapMove" type="boolean" default={true}>
  If `true`, refine is triggered as you move the map.

  ```js JavaScript icon=code theme={"system"}
  customGeoSearch({
    enableRefineOnMapMove: false,
  });
  ```
</ParamField>

<ParamField body="transformItems" type="function" default="items => items">
  A function that receives the list of items before they are displayed.
  It should return a new array with the same structure.
  Use this to transform, filter, or reorder the items.

  The function also has access to the full `results` data,
  including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/)
  and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters),
  such as `disjunctiveFacetsRefinements`.

  ```js JavaScript icon=code theme={"system"}
  customGeoSearch({
    transformItems(items) {
      return items.map((item) => ({
        ...item,
        name: item.name.toUpperCase(),
      }));
    },
  });

  // or, combined with results 
  customGeoSearch({
    transformItems(items, { results }) {
      return items.query.length === 0
        ? items
        : items.map((item) => ({
            ...item,
            name: `${item.name} (matching "${results.query}")`,
          }));
    },
  });
  ```
</ParamField>

### Full example

<CodeGroup>
  ```html HTML theme={"system"}
  <div id="geo-search"></div>
  ```

  ```js JavaScript theme={"system"}
  // Create the render function
  let map = null;
  let markers = [];
  let isUserInteraction = true;

  const renderGeoSearch = (renderOptions, isFirstRendering) => {
    const { items, currentRefinement, refine, clearMapRefinement, widgetParams } =
      renderOptions;

    const { initialZoom, initialPosition, container } = widgetParams;

    if (isFirstRendering) {
      const element = document.createElement("div");
      element.style.height = "100%";

      const button = document.createElement("button");
      button.textContent = "Clear the map refinement";

      map = L.map(element);

      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
      }).addTo(map);

      map.on("moveend", () => {
        if (isUserInteraction) {
          const ne = map.getBounds().getNorthEast();
          const sw = map.getBounds().getSouthWest();

          refine({
            northEast: { lat: ne.lat, lng: ne.lng },
            southWest: { lat: sw.lat, lng: sw.lng },
          });
        }
      });

      button.addEventListener("click", () => {
        clearMapRefinement();
      });

      container.appendChild(element);
      container.appendChild(button);
    }

    container.querySelector("button").hidden = !currentRefinement;

    markers.forEach((marker) => marker.remove());

    markers = items.map(({ _geoloc }) =>
      L.marker([_geoloc.lat, _geoloc.lng]).addTo(map),
    );

    isUserInteraction = false;
    if (!currentRefinement && markers.length) {
      map.fitBounds(L.featureGroup(markers).getBounds(), {
        animate: false,
      });
    } else if (!currentRefinement) {
      map.setView(initialPosition, initialZoom, {
        animate: false,
      });
    }
    isUserInteraction = true;
  };

  // Create the custom widget
  const customGeoSearch = connectGeoSearch(renderGeoSearch);

  // Instantiate the custom widget
  search.addWidgets([
    customGeoSearch({
      container: document.querySelector("#geo-search"),
      initialZoom: 12,
      initialPosition: {
        lat: 48.864716,
        lng: 2.349014,
      },
    }),
  ]);
  ```
</CodeGroup>
