leaflet-control-geocoder turns a Leaflet map into a place search box with a dozen backends
A simple geocoder form to locate places. Easily extended to multiple data providers.
At a glance
- What is it?
- leaflet-control-geocoder is a Leaflet plugin that adds a geocoder form to a map, defaulting to OpenStreetMap and Nominatim while supporting more than a dozen other providers through one interface. Version 4 is a TypeScript rewrite with a dual CommonJS and ES module build, and the project keeps four separate demo applications so the plugin can be exercised under different bundlers.
- Who is it for?
- Adopt leaflet-control-geocoder if you already build with Leaflet and want a place search without writing provider plumbing, since the provider list covers the common hosted geocoders and the extension point covers the rest. Do not adopt it if you need a server side lookup or a guaranteed quota, since the default provider is a public Nominatim instance and the README does not describe a rate limit policy.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 97 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One control, a form, and a provider switch underneath
leaflet-control-geocoder is a simple geocoder for Leaflet that by default uses OpenStreetMap and Nominatim. The README describes it in one line as a geocoder form to locate places, easily extended to multiple data providers, and the badge in the title line states Leaflet 1.0.0 compatible.
Adding it to a map is three statements, and the third is the whole integration:
const map = new L.Map('map').setView([0, 0], 2);
new L.TileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
new L.Control.Geocoder().addTo(map);The control creates the input, runs the query against the configured provider, and on a result the map centres on the location and places a marker there. That default behaviour is the piece you most often want to change, and the README covers it in the Customizing section with an example that turns the default handling off and draws a bounding box polygon instead:
var geocoder = L.Control.geocoder({
defaultMarkGeocode: false
})
.on('markgeocode', function (e) {
var bbox = e.geocode.bbox;Setting defaultMarkGeocode to false removes the control's own handler, and the markgeocode event then carries the geocode result so you can decide what to do with it. The result object exposes a bbox, which is what makes the polygon example possible.
The provider list is the real feature, and extension is a documented path
The list of supported data providers is long enough to change how you think about the plugin. The README names LatLng for parsing basic latitude and longitude strings, OSM and Nominatim, the Bing Locations API, the Google Geocoding API, Mapbox Geocoding, the MapQuest Geocoding API, What3Words, Photon, Pelias along with geocode.earth (formerly Mapzen Search) and Openrouteservice, the HERE Geocoder API including its legacy variant, the Neutrino API, Plus codes (formerly OpenLocationCode) which requires the open-location-code package, ArcGIS, and Azure Maps Geocoding.
Two of those entries are worth a second look. LatLng is not a network service at all, it is a local parser for coordinate strings, which means the control can accept a pasted position without any request. And Plus codes is the one entry with a stated dependency, the open-location-code npm package, because the decoding is done locally.
Everything else is a hosted HTTP geocoder, and the practical consequence is that the plugin abstracts the request shape but not the terms. A commercial provider with a paid tier and a key behaves differently from a public Nominatim instance, and the README does not cover quota or rate limits for any of them.
Extending the list is presented as normal practice rather than a hack. The README names two current extensions, DAWA Geocoder for the Danish Address Web API by Niels Kjøller Hansen, and BAN Geocoder for the French Base Adresse Nationale. Both are separate repositories built on this one, which is the clearest evidence that the provider interface is stable enough to build a whole project on.
Version 4 is a TypeScript rewrite with a deliberate dual build
The release history shows a major version landing on 2026-07-18, with 4.0.0 on that date and 3.3.1 and 3.3.0 in September and August 2025. The 4.0.0 release is a rewrite rather than a feature release, and the repository shows it.
The package is now type module with source in src/index.ts, and the exports map is deliberately split so that both module systems resolve to a suitable build:
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/Control.Geocoder.modern.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/Control.Geocoder.js"
}
},So an import gets the modern build and a require gets the classic one, with the same type declarations either way. The top level main and module fields point at the same two files for older resolvers, and the stylesheet is exported under three names, ./dist/Control.Geocoder.css, ./Control.Geocoder.css and ./style.css, which is a deliberate convenience for the several bundler conventions in circulation.
The build itself runs a bundler through a vp command and then compiles declarations separately with tsc using the ES2020 target, emitting declaration files only. The test script is vp test run --dom, so the test suite runs in a DOM environment, which is what a Leaflet plugin needs, and the spec/ directory at the root holds those tests.
The repository layout also shows a .vite-hooks/ directory and a vite.config.ts, and the demo directories are named for the bundler they exercise: demo-esbuild, demo-rollup, demo-webpack and demo-unpkg.
Four demo applications, each proving a different packaging path
The Demos section lists five entries, and the differences between them are the point. There is a hosted demo on liedman.net, a demo/ directory in the repository, a demo-unpkg/ that uses the unpkg.com content delivery network, a demo-rollup/ that uses the rollup.js bundler, and a demo-webpack/ that uses the webpack bundler. A demo-esbuild/ directory also exists at the root of the repository, even though the README's demo list does not mention it.
The build scripts mirror that. There is a build:demo script that chains three of them, and separate scripts for each, each of which changes into its own directory, installs and builds:
"build:demo": "npm run build:demo-esbuild && npm run build:demo-rollup && npm run build:demo-webpack",
"build:demo-esbuild": "cd demo-esbuild && npm install && npm run build",A plugin that ships a single prebuilt file can easily break under a bundler that applies its own transformations, and four demos covering esbuild, rollup, webpack and a plain CDN include is a reasonable answer to that. It also means the maintainer finds out about packaging regressions before users do.
The API documentation has a versioning wrinkle worth knowing. The README points the latest docs at a hosted URL, and then points version 1.13.0 at the GitHub tree for that tag. So the API reference for the current release lives on the author's site, and older documentation is read from the repository at a specific tag. If you are reading the README of a version other than the newest, the matching API section is the one in the tree for your tag.
Installing from a release or a CDN, without a build step
There is no install command in the README, because the plugin is designed to be dropped into a page. The usage section says to download the latest release, or obtain the latest release via unpkg.com, and then shows the two tags that do it:
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>Note that the file names here are the classic build, matching what the require condition in the exports map resolves to. If you are on a modern module setup you would import from the package instead, and the unpkg demo directory exists to show that path with a CDN.
The npm package is published under the name leaflet-control-geocoder, and the repository is at perliedman/leaflet-control-geocoder with the default branch master. The package description lists the built in providers as OpenStreetMap Nominatim, Bing, Google, Mapbox, MapQuest, What3Words, Photon, Pelias, HERE, Neutrino and Plus codes, and the keywords in the metadata cover leaflet, geocoder, search, nominatim, osm, openstreetmap, geocode, autocomplete, searchbox, address, poi, place, location, latlng, lnglat and geolocation.
There is also a small point about the file list. The package files array includes a LICENCE entry, while the repository root carries a LICENSE file, and the licence recorded for the project is BSD-2-Clause, a permissive two clause grant with no copyleft obligation on code you ship. That is a licensing note rather than legal advice, and it does not affect the terms of the geocoding services you call.
A community statement at the top of the README
Before the project description, the README opens with a section titled A few words on diversity in tech. It is a personal statement rather than a technical document, and it asks readers who disagree with its position not to use the open source the author has published, and says the author is not interested in contributions from people who cannot act respectfully towards others.
Worth flagging because it affects adoption in two ways. First, it signals that the maintainer holds a strong position on conduct, which usually means an active and moderated project community rather than an abandoned one. Second, it is a stated condition of use expressed by the author, and it sits alongside a permissive code licence, so if your organisation has a policy on what open source you adopt, that is a factor to weigh alongside the technical fit.
On the technical side, the maintenance record is more useful. The last push was on 2026-07-18, the same day as the 4.0.0 release, and the repository is not archived. So the 4.0.0 rewrite is recent rather than abandoned, and the type rewrite means the 3.x API documentation is not a reliable guide to the current one.
The alternative to this plugin is to call a geocoding API yourself and handle the result. The difference in approach is that leaflet-control-geocoder gives you the input, the request wiring for a dozen providers and the map behaviour, and leaves you the rendering. If you already have a place search UI and only want the map to fly to a result, the plugin is more than you need, and a plain fetch to your chosen provider with a map.setView call is shorter code.
Editorial conclusion
Adopt leaflet-control-geocoder if you already build with Leaflet and want a place search without writing provider plumbing, since the provider list covers the common hosted geocoders and the extension point covers the rest. Do not adopt it if you need a server side lookup or a guaranteed quota, since the default provider is a public Nominatim instance and the README does not describe a rate limit policy. Verify first which provider you will actually call and what its terms are, that your bundler resolves the exports map correctly for the module format you target, and that the geocode event handling matches your own result rendering, because the default handler centres the map and drops a marker.
Frequently asked questions
How do I add a geocoder search box to a Leaflet map?
Add the control with new L.Control.Geocoder().addTo(map) after creating the map and adding a tile layer. By default the control uses OpenStreetMap and Nominatim, centres the map on a result and places a marker there.
Which geocoding providers does leaflet-control-geocoder support?
The README lists LatLng for parsing coordinate strings, OSM and Nominatim, Bing, Google, Mapbox, MapQuest, What3Words, Photon, Pelias, geocode.earth, Openrouteservice, HERE, Neutrino, Plus codes, ArcGIS and Azure Maps Geocoding. Plus codes requires the open-location-code npm package.
How do I change what happens when a geocode result is selected?
Set the defaultMarkGeocode option to false to remove the control's default handler, and listen to the control's markgeocode event instead. The event carries the geocode result, including its bbox, which the README example uses to draw a polygon and fit the map to it.
How do I add a geocoding provider that is not built in?
The README says the plugin can easily be extended to support other providers and names two existing extensions, DAWA Geocoder for the Danish Address Web API and BAN Geocoder for the French Base Adresse Nationale, each built as a separate repository on top of this one.
What changed in version 4 of leaflet-control-geocoder?
Version 4.0.0 was published on 2026-07-18 and the package is now a TypeScript project with type module, a source field pointing at src/index.ts and an exports map that serves a modern build to import and a classic build to require, with shared type declarations. The licence is BSD-2-Clause and the last push was on the same date.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/perliedman-leaflet-control-geocoder)Was this useful?