Skip to content

Add a Geocoder Search Control to Leaflet

This example adds a search control to the top right corner of a Leaflet map using the leaflet-control-geocoder plugin. Searching for an address calls the Maptoolkit Geocoding API and centers the map on the result. Try searching for Sillgasse.

Dependencies: leaflet-control-geocoder

let map = L.map("map").setView([47.26816, 11.40037], 13);

    L.tileLayer("https://rtc-cdn.maptoolkit.net/rtc/maptoolkit-maptoolkit.summer/{z}/{x}/{y}{ratio}.png?api_key=YOUR_API_KEY", {
      ratio: L.Browser.retina ? "@2x" : "",
      maxZoom: 18,
      attribution: "© <a href='https://www.maptoolkit.com' target='_blank'>Maptoolkit</a> © <a href='https://www.openstreetmap.org/copyright' target='_blank'>OSM</a>",
    }).addTo(map);

    L.Control.geocoder({
      geocoder: {
        geocode: (query, callback, context) => {
          fetch(`https://geocoder.maptoolkit.net/search?q=${encodeURIComponent(query)}&language=en&api_key=YOUR_API_KEY`)
            .then((r) => r.json())
            .then((result) => {
              callback.call(context, result.map((e) => ({
                bbox: [[e.boundingbox[0], e.boundingbox[2]], [e.boundingbox[1], e.boundingbox[3]]],
                center: [e.lat, e.lon],
                name: e.display_name,
              })));
            });
        },
        suggest: function(query, callback, context) {
          return this.geocode(query, callback, context);
        },
      }
    }).addTo(map);
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css" />
  <script src="https://cdn.jsdelivr.net/npm/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-control-geocoder/dist/Control.Geocoder.css" />
  <style>
    html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
    #map { width: 100%; height: 100%; }
  </style>
</head>
<body>
  <div id="map"></div>
  <script>
    let map = L.map("map").setView([47.26816, 11.40037], 13);

    L.tileLayer("https://rtc-cdn.maptoolkit.net/rtc/maptoolkit-maptoolkit.summer/{z}/{x}/{y}{ratio}.png?api_key=YOUR_API_KEY", {
      ratio: L.Browser.retina ? "@2x" : "",
      maxZoom: 18,
      attribution: "© <a href='https://www.maptoolkit.com' target='_blank'>Maptoolkit</a> © <a href='https://www.openstreetmap.org/copyright' target='_blank'>OSM</a>",
    }).addTo(map);

    L.Control.geocoder({
      geocoder: {
        geocode: (query, callback, context) => {
          fetch(`https://geocoder.maptoolkit.net/search?q=${encodeURIComponent(query)}&language=en&api_key=YOUR_API_KEY`)
            .then((r) => r.json())
            .then((result) => {
              callback.call(context, result.map((e) => ({
                bbox: [[e.boundingbox[0], e.boundingbox[2]], [e.boundingbox[1], e.boundingbox[3]]],
                center: [e.lat, e.lon],
                name: e.display_name,
              })));
            });
        },
        suggest: function(query, callback, context) {
          return this.geocode(query, callback, context);
        },
      }
    }).addTo(map);
  </script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create a map of Innsbruck with a geocoder in the top right corner, using Leaflet.

How it works

leaflet-control-geocoder supplies the search box and expects a geocoder object with a geocode method, so this example supplies one that calls Maptoolkit instead of the plugin’s default provider.

The response is Nominatim-shaped, which explains several details that otherwise look arbitrary. lat and lon come back as strings, not numbers, so they need coercing before a map will accept them. display_name is the full formatted address. boundingbox is [south, north, west, east], also as strings, which is a different order from the [west, south, east, north] that most GeoJSON tooling expects.

The query has to be URL-encoded. encodeURIComponent is not optional: an address with a comma or an ampersand truncates the query without it.

The callback needs bbox as [[south, west], [north, east]], which is why the four boundingbox entries are reshuffled rather than passed straight through.

suggest delegates to geocode, so typing queries the service on each keystroke. If you are watching request counts, debounce it or drop suggest entirely.

Next steps

Search is half of geocoding. Reverse geocoding turns a coordinate back into an address, which is what a click-anywhere or drag-a-pin interface needs, and the Geocoding API reference covers both directions along with the parameters that narrow results by country or type.

A found address is usually a starting point rather than a result. The Routing API takes the coordinates straight from a search result, which is the whole from-and-to interface in two calls.