Source: https://developers.woosmap.com/products/localities/guides/js-guide/

> For clean Markdown of any page, append `.md` to the page URL.

> For a complete documentation index, see https://developers.woosmap.com/llms.txt

# Localities JS API Guide



The **Woosmap Localities JS API** lets you integrate location search and geocoding into your web applications with flexible, programmatic control.

## Sample Usage

This example shows how to use the Localities JS API for autocomplete and details retrieval.

https://demo.woosmap.com/js-samples/samples/localities-js-api/app/dist/
[](https://demo.woosmap.com/js-samples/samples/localities-js-api/highlight/highlight.html "Open in new tab with highlighted code")
Try sample 

- [CodeSandbox](https://codesandbox.io/p/devbox/github/woosmap/js-samples/tree/master/dist/samples/localities-js-api/app?file=index.ts)
- [JsFiddle](https://jsfiddle.net/gh/get/library/pure/woosmap/js-samples/tree/master/dist/samples/localities-js-api/jsfiddle)
- [Clone on Github](https://github.com/Woosmap/js-samples/tree/sample/localities-js-api)

```typescript
const customDescription =
  'postal_code:"{name} ({postal_town}) - {administrative_area_level_0}"';
let localitiesAutocompleteService;
let inputElement: HTMLInputElement;
let suggestionsList: HTMLUListElement;
let clearSearchBtn: HTMLButtonElement;
let responseElement: HTMLElement;

function init(): void {
  if (inputElement && suggestionsList) {
    inputElement.addEventListener("input", handleAutocomplete);
    inputElement.addEventListener("keydown", (event) => {
      if (event.key === "Enter") {
        const firstLi = suggestionsList.querySelector("li");
        if (firstLi) {
          firstLi.click();
        }
      }
    });
  }
  clearSearchBtn.addEventListener("click", () => {
    inputElement.value = "";
    suggestionsList.style.display = "none";
    clearSearchBtn.style.display = "none";
    responseElement.style.display = "none";
    inputElement.focus();
  });

  localitiesAutocompleteService =
    new window.woosmap.localities.AutocompleteService("YOUR_API_KEY");
}

function handleAutocomplete(): void {
  if (inputElement && suggestionsList) {
    const input = inputElement.value;
    input.replace('"', '\"').replace(/^\s+|\s+$/g, "");
    if (input !== "") {
      localitiesAutocompleteService.autocomplete(
        { input, custom_description: customDescription },
        displaySuggestions,
        (errorCode: number, errorText: string) => {
          console.error(
            `Error autocomplete localities: ${errorCode}:${errorText}`,
          );
        },
        0,
      );
    } else {
      suggestionsList.style.display = "none";
      clearSearchBtn.style.display = "none";
    }
  }
}

function displaySuggestions({
  localities,
}: woosmap.map.localities.LocalitiesAutocompleteResponse) {
  if (inputElement && suggestionsList) {
    suggestionsList.innerHTML = "";
    if (localities.length > 0) {
      localities.forEach((locality) => {
        const li = document.createElement("li");
        li.innerHTML = formatPredictionList(locality) ?? "";
        li.addEventListener("click", () => {
          inputElement.value = locality.description ?? "";
          suggestionsList.style.display = "none";
          localitiesAutocompleteService.getDetails(
            locality.public_id,
            displayLocalitiesResponse,
          );
        });
        suggestionsList.appendChild(li);
      });
      suggestionsList.style.display = "block";
      clearSearchBtn.style.display = "block";
    } else {
      suggestionsList.style.display = "none";
    }
  }
}

function formatPredictionList(locality): string {
  const prediction = locality;
  const predictionClass = "no-viewpoint";
  const matched_substrings = prediction.matched_substrings;
  let formatted_name = "";
  if (
    prediction.matched_substrings &&
    prediction.matched_substrings.description
  ) {
    formatted_name = bold_matched_substring(
      prediction["description"],
      matched_substrings.description,
    );
  } else {
    formatted_name = prediction["description"];
  }

  let html = "";
  html += `<div class="prediction ${predictionClass}">${formatted_name}</div>`;

  return html;
}

function displayLocalitiesResponse(
  selectedLocality: woosmap.map.localities.LocalitiesPredictions,
) {
  if (responseElement) {
    responseElement.innerHTML = `<code>${JSON.stringify(selectedLocality, null, 2)}</code>`;
    responseElement.style.display = "block";
  }
}

function bold_matched_substring(string: string, matched_substrings: string[]) {
  matched_substrings = matched_substrings.reverse();
  for (const substring of matched_substrings) {
    const char = string.substring(
      substring["offset"],
      substring["offset"] + substring["length"],
    );
    string = `${string.substring(
      0,
      substring["offset"],
    )}<span class='bold'>${char}</span>${string.substring(
      substring["offset"] + substring["length"],
    )}`;
  }
  return string;
}

document.addEventListener("DOMContentLoaded", () => {
  inputElement = document.getElementById(
    "autocomplete-input",
  ) as HTMLInputElement;
  suggestionsList = document.getElementById(
    "suggestions-list",
  ) as HTMLUListElement;
  clearSearchBtn = document.getElementsByClassName(
    "clear-searchButton",
  )[0] as HTMLButtonElement;
  responseElement = document.getElementById(
    "response-container",
  ) as HTMLElement;
  init();
});

declare global {
  interface Window {
    // currently, the localities JS API typings are not exported, so we use `any` here
    woosmap: {
      localities: {
        AutocompleteService: new (key: string) => any;
      };
    };
  }
}
```

```javascript
const customDescription =
  'postal_code:"{name} ({postal_town}) - {administrative_area_level_0}"';
let localitiesAutocompleteService;
let inputElement;
let suggestionsList;
let clearSearchBtn;
let responseElement;

function init() {
  if (inputElement && suggestionsList) {
    inputElement.addEventListener("input", handleAutocomplete);
    inputElement.addEventListener("keydown", (event) => {
      if (event.key === "Enter") {
        const firstLi = suggestionsList.querySelector("li");

        if (firstLi) {
          firstLi.click();
        }
      }
    });
  }

  clearSearchBtn.addEventListener("click", () => {
    inputElement.value = "";
    suggestionsList.style.display = "none";
    clearSearchBtn.style.display = "none";
    responseElement.style.display = "none";
    inputElement.focus();
  });
  localitiesAutocompleteService =
    new window.woosmap.localities.AutocompleteService("YOUR_API_KEY");
}

function handleAutocomplete() {
  if (inputElement && suggestionsList) {
    const input = inputElement.value;

    input.replace('"', '\"').replace(/^\s+|\s+$/g, "");
    if (input !== "") {
      localitiesAutocompleteService.autocomplete(
        { input, custom_description: customDescription },
        displaySuggestions,
        (errorCode, errorText) => {
          console.error(
            `Error autocomplete localities: ${errorCode}:${errorText}`,
          );
        },
        0,
      );
    } else {
      suggestionsList.style.display = "none";
      clearSearchBtn.style.display = "none";
    }
  }
}

function displaySuggestions({ localities }) {
  if (inputElement && suggestionsList) {
    suggestionsList.innerHTML = "";
    if (localities.length > 0) {
      localities.forEach((locality) => {
        const li = document.createElement("li");

        li.innerHTML = formatPredictionList(locality) ?? "";
        li.addEventListener("click", () => {
          inputElement.value = locality.description ?? "";
          suggestionsList.style.display = "none";
          localitiesAutocompleteService.getDetails(
            locality.public_id,
            displayLocalitiesResponse,
          );
        });
        suggestionsList.appendChild(li);
      });
      suggestionsList.style.display = "block";
      clearSearchBtn.style.display = "block";
    } else {
      suggestionsList.style.display = "none";
    }
  }
}

function formatPredictionList(locality) {
  const prediction = locality;
  const predictionClass = "no-viewpoint";
  const matched_substrings = prediction.matched_substrings;
  let formatted_name = "";

  if (
    prediction.matched_substrings &&
    prediction.matched_substrings.description
  ) {
    formatted_name = bold_matched_substring(
      prediction["description"],
      matched_substrings.description,
    );
  } else {
    formatted_name = prediction["description"];
  }

  let html = "";

  html += `<div class="prediction ${predictionClass}">${formatted_name}</div>`;
  return html;
}

function displayLocalitiesResponse(selectedLocality) {
  if (responseElement) {
    responseElement.innerHTML = `<code>${JSON.stringify(selectedLocality, null, 2)}</code>`;
    responseElement.style.display = "block";
  }
}

function bold_matched_substring(string, matched_substrings) {
  matched_substrings = matched_substrings.reverse();

  for (const substring of matched_substrings) {
    const char = string.substring(
      substring["offset"],
      substring["offset"] + substring["length"],
    );

    string = `${string.substring(0, substring["offset"])}<span class='bold'>${char}</span>${string.substring(substring["offset"] + substring["length"])}`;
  }
  return string;
}

document.addEventListener("DOMContentLoaded", () => {
  inputElement = document.getElementById("autocomplete-input");
  suggestionsList = document.getElementById("suggestions-list");
  clearSearchBtn = document.getElementsByClassName("clear-searchButton")[0];
  responseElement = document.getElementById("response-container");
  init();
});
```

```css
/*
 * Always set the map height explicitly to define the size of the div element
 * that contains the map.
 */
#map {
  height: 100%;
}

/*
 * Optional: Makes the sample page fill the window.
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}

#autocomplete-container {
  display: flex;
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  background: #fff;
  border-radius: 12px;
  padding: 0 12px;
  max-width: 320px;
  width: 100%;
  height: 42px;
  border: none;
  box-sizing: border-box;
  align-items: center;
  cursor: text;
  font-size: 15px;
}

#autocomplete-container .search-icon, #autocomplete-container .clear-icon {
  color: inherit;
  flex-shrink: 0;
  height: 16px;
  width: 16px;
}

#autocomplete-container .clear-icon {
  transform: scale(1.3);
}

#autocomplete-input {
  box-sizing: border-box;
  padding: 0;
  height: 40px;
  line-height: 24px;
  vertical-align: top;
  transition-property: color;
  transition-duration: 0.3s;
  width: 100%;
  text-overflow: ellipsis;
  background: transparent;
  border-radius: 0;
  border: 0;
  margin: 0 8px;
  outline: 0;
  overflow: visible;
  appearance: textfield;
  font-size: 100%;
}

.clear-searchButton {
  display: none;
  height: 18px;
  width: 22px;
  background: none;
  border: none;
  vertical-align: middle;
  pointer-events: all;
  cursor: pointer;
}

#suggestions-list {
  border-radius: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  box-sizing: border-box;
  position: absolute;
  max-width: 320px;
  width: 100%;
  top: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  max-height: 80vh;
  margin: 5px 0 0;
  padding: 0;
  display: none;
  overflow-y: auto;
  background-color: #fff;
}

#suggestions-list.visible {
  display: block;
}

#suggestions-list li {
  padding: 12px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#suggestions-list li:hover {
  background-color: #f2f2f2;
}

body {
  background-color: #eee;
  overflow: hidden;
}

#app {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 100vh;
  padding: 10px;
}

.search-container {
  position: relative;
  min-height: 45px;
}

.search-container #autocomplete-container {
  top: 0;
  left: 0;
}

pre {
  display: none;
  margin-top: 10px;
  margin-bottom: 20px;
  padding: 10px;
  border: 3px solid #c8c8c8;
  background-color: white;
  border-radius: 8px;
  overflow-y: auto;
  overflow-x: auto;
  font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
}

.bold {
  font-weight: 700;
}

.title {
  margin-block-end: 0;
  font-weight: 500;
}

.note {
  margin-block-start: 4px;
  font-size: 13px;
}
```

```html
<html>
  <head>
    <title>Localities JS API</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8" />

    <script src="https://sdk.woosmap.com/localities/localities.2.0.js"></script>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="app">
      <p class="title">Autocomplete input with Woosmap Localities JS API</p>
      <p class="note">
        <em
          >Custom Description for Postal Codes "{name} ({postal_town}) -
          {administrative_area_level_0}"</em
        >
      </p>
      <div class="search-container">
        <div id="autocomplete-container">
          <svg class="search-icon" viewBox="0 0 16 16">
            <path
              d="M3.617 7.083a4.338 4.338 0 1 1 8.676 0 4.338 4.338 0 0 1-8.676 0m4.338-5.838a5.838 5.838 0 1 0 2.162 11.262l2.278 2.279a1 1 0 0 0 1.415-1.414l-1.95-1.95A5.838 5.838 0 0 0 7.955 1.245"
              fill-rule="evenodd"
              clip-rule="evenodd"
            ></path>
          </svg>

          <input
            type="text"
            id="autocomplete-input"
            placeholder="Search a locality or a postal code..."
            autocomplete="off"
          />
          <button aria-label="Clear" class="clear-searchButton" type="button">
            <svg class="clear-icon" viewBox="0 0 24 24">
              <path
                d="M7.074 5.754a.933.933 0 1 0-1.32 1.317L10.693 12l-4.937 4.929a.931.931 0 1 0 1.319 1.317l4.938-4.93 4.937 4.93a.933.933 0 0 0 1.581-.662.93.93 0 0 0-.262-.655L13.331 12l4.937-4.929a.93.93 0 0 0-.663-1.578.93.93 0 0 0-.656.261l-4.938 4.93z"
              ></path>
            </svg>
          </button>
          <ul id="suggestions-list"></ul>
        </div>
      </div>
      <pre id="response-container"></pre>
    </div>
  </body>
</html>
```

* * *

## Quick Start Checklist

1. **Add the JS API script** to your HTML:

```html
<script src="https://sdk.woosmap.com/localities/localities.2.0.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css" />
```

2. **Create the input and response elements:**

```html
<input id="autocomplete-input" type="text" placeholder="Search a locality or a postal code..." />
<pre id="response-container"></pre>
```

3. **Basic JS setup:**

```javascript
const key = 'YOUR_API_KEY';
const autocompleteService = new woosmap.localities.AutocompleteService(key);
// Listen to input events and call autocompleteService.autocomplete()
// On selection, call autocompleteService.getDetails()
```

4. **Add basic CSS for layout:**

```css
#autocomplete-input { width: 100%; padding: 8px; font-size: 16px; }
#response-container { background: #f9f9f9; border: 1px solid #eee; padding: 12px; border-radius: 4px; margin-top: 12px; display: none; }
```

* * *

## Main Concepts

- **Autocomplete:** Listen to input events, call `autocompleteService.autocomplete()` to get suggestions.
- **Custom Description:** Use the `custom_description` parameter to format suggestions (see sample for details).
- **Selection & Details:** When a user selects a suggestion, call `autocompleteService.getDetails()` to fetch full details and display them.
- **UI Handling:** Show/hide suggestions, clear input, and display results using basic DOM manipulation and CSS.
- **Accessibility & UX:** Add keyboard support, clear button, and highlight matched substrings for a better experience.

For a complete working example, see the embedded sample and code tabs above.

* * *

## Settings Overview

- See the [Localities JS API Reference](/products/localities/reference/libraries/js/2.0/) for all available methods and options.
- Use `autocomplete()` for suggestions and `getDetails()` for retrieving full details.
- Configure parameters such as `custom_description`, `types`, `components`, and `language` for tailored results.

* * *

## Troubleshooting & Best Practices

- **API Key Issues:** Ensure your key is valid and domain restrictions are set in the Woosmap Console.
- **Minimum Input Length:** Use a sensible minimum length (e.g. 3) to avoid unnecessary API calls.
- **Styling:** Customize your input and results display with CSS for a better user experience.
- **Accessibility:** Make sure your input is accessible (label, ARIA attributes).

* * *

## Resources

- [Localities JS API Reference](/products/localities/reference/libraries/js/2.0/)
- [Autocomplete API Docs](/products/localities/features/autocomplete/)
- [Sample Integrations](/products/localities/guides/widget-guide/#sample-usage)
- [Woosmap Console](https://console.woosmap.com/)
