maplibre-gl-basemap-control
v0.13.0
Published
A MapLibre GL JS control for searching and switching public basemaps
Maintainers
Readme
MapLibre GL Basemap Control
A MapLibre GL JS control for searching and switching public basemaps. It keeps the standard compact MapLibre control button, opens a floating searchable panel, and can be used from vanilla TypeScript or React.
Features
- Search-first basemap picker inspired by QuickMapServices
- Built-in catalog for common public basemaps, MapTiler styles, Amazon Location styles, Mapbox styles, Maptoolkit styles, Protomaps styles, and Stadia Maps basemaps
- Stackable traffic overlays for TomTom, HERE, Mapbox, and Google
- Custom basemap and provider definitions
- MapLibre
IControlimplementation - React wrapper and state hook
- Vite library build with ESM/CJS outputs and TypeScript declarations
- Docker and GitHub Actions examples workflow
Installation
npm install maplibre-gl-basemap-control maplibre-glVanilla TypeScript
import maplibregl from 'maplibre-gl';
import { BasemapControl } from 'maplibre-gl-basemap-control';
import 'maplibre-gl-basemap-control/style.css';
const map = new maplibregl.Map({
container: 'map',
style: {
version: 8,
sources: {},
layers: [{ id: 'background', type: 'background' }],
},
center: [0, 0],
zoom: 2,
});
map.on('load', () => {
const basemaps = new BasemapControl({
title: 'Basemaps',
collapsed: true,
defaultBasemapId: 'carto-positron',
});
map.addControl(basemaps, 'top-right');
});React
import { useEffect, useRef, useState } from 'react';
import maplibregl, { Map } from 'maplibre-gl';
import { BasemapControlReact, useBasemapState } from 'maplibre-gl-basemap-control/react';
import 'maplibre-gl-basemap-control/style.css';
function App() {
const mapContainer = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<Map | null>(null);
const { state, setState } = useBasemapState({
collapsed: false,
activeBasemapId: 'carto-positron',
});
useEffect(() => {
if (!mapContainer.current) return;
const mapInstance = new maplibregl.Map({
container: mapContainer.current,
style: { version: 8, sources: {}, layers: [{ id: 'background', type: 'background' }] },
center: [0, 0],
zoom: 2,
});
mapInstance.on('load', () => setMap(mapInstance));
return () => mapInstance.remove();
}, []);
return (
<>
<div ref={mapContainer} />
{map && (
<BasemapControlReact
map={map}
collapsed={state.collapsed}
activeBasemapId={state.activeBasemapId}
onStateChange={setState}
onBasemapChange={(basemap) => console.log(basemap.name)}
/>
)}
</>
);
}Custom Basemaps
The built-in catalog can be extended or replaced.
import type { BasemapDefinition } from 'maplibre-gl-basemap-control';
const customBasemaps: BasemapDefinition[] = [
{
id: 'example-raster',
name: 'Example Raster',
provider: 'example',
type: 'raster',
category: 'Custom',
attribution: '© Example Provider',
source: {
type: 'raster',
tiles: ['https://tiles.example.com/{z}/{x}/{y}.png'],
tileSize: 256,
maxzoom: 19,
},
tags: ['custom', 'street'],
},
];
const control = new BasemapControl({
basemaps: customBasemaps,
providers: [{ id: 'example', name: 'Example Provider', category: 'Custom' }],
includeDefaultBasemaps: true,
});Set includeDefaultBasemaps: false to use only your supplied catalog.
Keyed Provider Styles
The built-in catalog includes MapTiler styles such as Streets, Base, Dataviz, Outdoor, Topo, Satellite Hybrid, Satellite Plain, Aquarelle, Backdrop, Landscape, Ocean, Toner, OpenStreetMap, and Winter. It also includes Amazon Location styles: Standard, Monochrome, Hybrid, and Satellite. Mapbox styles include Streets, Outdoors, Light, Dark, Satellite, Satellite Streets, Navigation Day, and Navigation Night. Protomaps styles include Light, Dark, White, Black, Grayscale, and Contrast.
MapTiler, Amazon Location, and Protomaps styles require API keys. Mapbox styles require an access token. Users can enter keys and tokens in the dedicated API keys view, opened from the key button in the panel header, or you can provide them when creating the control. When a basemap is selected before its key is set, the control surfaces the error and the matching credential field inline, before any destructive style change, so the missing key can be entered and the basemap retried with Enter.
const control = new BasemapControl({
defaultBasemapId: 'maptiler-streets',
mapTilerApiKey: 'YOUR_MAPTILER_API_KEY',
amazonApiKey: 'YOUR_AMAZON_LOCATION_API_KEY',
awsRegion: 'us-east-1',
mapboxAccessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
protomapsApiKey: 'YOUR_PROTOMAPS_API_KEY',
stadiaApiKey: 'YOUR_STADIA_MAPS_API_KEY',
});The default MapTiler style URLs follow this form:
https://api.maptiler.com/maps/{mapId}/style.json?key={api-key}Amazon Location style URLs follow this form:
https://maps.geo.{aws-region}.amazonaws.com/v2/styles/{mapStyle}/descriptor?key={api-key}Mapbox style URLs follow this form:
https://api.mapbox.com/styles/v1/mapbox/{styleId}?access_token={api-key}Protomaps style URLs follow this form:
https://api.protomaps.com/styles/v5/{styleId}/en.json?key={api-key}Stadia Maps and Stamen
The catalog ships Stadia Maps' own styles (Alidade Smooth, Alidade Smooth Dark, Alidade Satellite, Outdoors, OSM Bright) and the Stadia x Stamen classics (Toner, Toner Lite, Toner Background, Toner Labels, Terrain, Terrain Background, Terrain Labels, Watercolor). Stadia has hosted the Stamen tilesets since 2023.
These are raster tiles that take the key directly on each tile URL:
https://tiles.stadiamaps.com/tiles/{slug}/{z}/{x}/{y}.png?api_key={api-key}Alidade Satellite and Stamen Watercolor are served as .jpg instead. The _labels layers are
transparent overlays, so pair them with allowMultiple: true to stack them over a base layer.
Note. Stadia also supports keyless access from allowlisted domains (localhost included), but this control always sends
api_key, so a key is required here. SetstadiaApiKeyor enter it in the panel's API keys view.
Traffic Overlays
The catalog ships real-time traffic overlays in the Traffic category. They are
stackable overlays rather than full basemaps, so enable allowMultiple: true (or
toggle "Add basemaps" in the panel) to lay them on top of any basemap. Click an
active traffic layer again to remove it.
| Basemap id | Provider | Credential |
|------------|----------|------------|
| tomtom-traffic-flow-relative, tomtom-traffic-flow-absolute, tomtom-traffic-flow-relative-delay | TomTom | tomtomApiKey |
| here-traffic-flow | HERE | hereApiKey |
| mapbox-traffic | Mapbox | mapboxAccessToken |
| google-traffic | Google | googleMapsApiKey |
const control = new BasemapControl({
allowMultiple: true,
tomtomApiKey: 'YOUR_TOMTOM_API_KEY',
hereApiKey: 'YOUR_HERE_API_KEY',
mapboxAccessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
});The TomTom and HERE overlays are transparent raster flow tiles; Mapbox Traffic is
a vector overlay colored by congestion level. Google Traffic uses the
Map Tiles API:
the control creates a tile session (with layerTypes: ['layerTraffic']) using your
key, caches the session token until it expires, then loads the traffic tiles. The
key must have the Map Tiles API enabled.
Troubleshooting
API_KEY_SERVICE_BLOCKED("Requests to this API tile method ...Bootstrap are blocked"): this is a key restriction, not project API enablement. Enabling the Map Tiles API on the project is not enough if the key itself has an API allow-list. In Cloud Console go to APIs & Services → Credentials → your key → API restrictions and either choose "Don't restrict key" or add Map Tiles API to the allowed list, then save (changes can take a few minutes to propagate). Make sure billing is enabled on the project too.
The same googleMapsApiKey also upgrades the base Google basemaps
(google-maps, google-satellite, google-terrain, google-hybrid). By
default these use the public keyless mt1.google.com xyz tiles, so they work out
of the box; when a key is set they load from the authorized Map Tiles API instead
(via a tile session, like Google Traffic).
In the catalog definition this is the split between source.tiles and
source.sessionTiles: tiles always holds the public keyless template, so
reading source.tiles without a key gives a directly usable URL, while
sessionTiles holds the Map Tiles API template (with its {session} and
{api-key} placeholders) that is only used once a key is configured. Basemaps
that cannot work keylessly, such as google-traffic, have no sessionTiles and
keep the session template in tiles.
Licensing caveat. The keyless
mt1.google.comtiles are Google Maps' internal endpoints. They are not covered by any public or open license, and accessing Google map content outside an official Google Maps Platform API violates the Google Maps Platform Terms of Service. They are convenient for local development and demos, but Google may rate-limit, change, or block them at any time. For production use, set agoogleMapsApiKeyso these basemaps use the authorized Map Tiles API (which requires the Map Tiles API enabled and billing on your project).
Maptoolkit Styles
The catalog ships the seven Maptoolkit vector styles. They need no
API key and load straight from https://styles.maptoolkit.org/{styleId}.json:
| Basemap id | Style | Category |
|------------|-------|----------|
| maptoolkit-summer | summer | Outdoor |
| maptoolkit-light | light | Light |
| maptoolkit-hiking | hiking | Outdoor |
| maptoolkit-cycling | cycling | Cycling |
| maptoolkit-winter | winter | Outdoor |
| maptoolkit-dark | dark | Dark |
| maptoolkit-street | street | Street |
Attribution. The Maptoolkit style JSONs carry no
attributionon their sources, so the catalog supplies the required "© Maptoolkit © OSM" credit links. Maptoolkit additionally requires the Maptoolkit logo (at least 24px tall) to be visible and unobstructed on the map; add it to your own UI, since the control cannot render it for you.
API
BasemapControl Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| collapsed | boolean | true | Whether the panel starts collapsed |
| position | string | 'top-right' | Preferred control position |
| title | string | 'Basemaps' | Panel title and button label |
| panelWidth | number | 340 | Floating panel width in pixels |
| className | string | '' | Extra class for the control button container |
| mapTilerApiKey | string | undefined | Initial MapTiler API key for built-in MapTiler styles |
| amazonApiKey | string | undefined | Initial Amazon Location API key for built-in Amazon styles |
| awsRegion | string | 'us-east-1' | AWS region for built-in Amazon Location styles |
| mapboxAccessToken | string | undefined | Initial Mapbox access token for built-in Mapbox styles and the Mapbox Traffic overlay |
| protomapsApiKey | string | undefined | Initial Protomaps API key for built-in Protomaps styles |
| stadiaApiKey | string | undefined | Initial Stadia Maps API key for built-in Stadia and Stadia x Stamen basemaps |
| tomtomApiKey | string | undefined | Initial TomTom API key for the TomTom Traffic overlays |
| hereApiKey | string | undefined | Initial HERE API key for the HERE Traffic overlay |
| googleMapsApiKey | string | undefined | Initial Google Maps API key (Map Tiles API) for the Google Traffic overlay and the base Google Maps/Satellite/Terrain/Hybrid basemaps (which fall back to keyless tiles without a key) |
| basemaps | BasemapDefinition[] | [] | Custom basemaps to add or use |
| providers | BasemapProvider[] | [] | Custom provider labels |
| includeDefaultBasemaps | boolean | true | Include the built-in public catalog |
| defaultBasemapId | string | undefined | Basemap to apply after the control is added |
| allowMultiple | boolean | false | Stack raster basemaps instead of replacing the active one |
| showMultipleToggle | boolean | true | Show the in-panel toggle that switches between adding and replacing |
| resizable | boolean | true | Allow resizing the panel by dragging its bottom-left or bottom-right corner |
| confirmStyleReplace | (confirmation) => boolean \| Promise<boolean> | undefined | Confirm before a style basemap replaces stacked raster basemaps (only invoked in allowMultiple mode with at least one raster stacked); return false to cancel |
Multiple Basemaps
By default, selecting a basemap replaces the active one. Set allowMultiple: true
to stack raster basemaps instead: each click adds the selected raster basemap as
an additional overlay, and clicking an already-active raster basemap removes it.
This lets a project hold several raster basemaps at once and switch between them by
toggling the entries in the panel.
const control = new BasemapControl({
allowMultiple: true,
});The panel also shows an Add basemaps toggle so users can switch between adding
and replacing at runtime. Hide it with showMultipleToggle: false.
Resizable Panel
The panel can be resized by dragging either of its bottom corners (bottom-left or
bottom-right). The resized width and height are reflected in state.panelWidth and
state.panelHeight. Set resizable: false to disable the handles.
Style basemaps cannot stack because they replace the entire map style, so selecting
a style basemap always replaces the active basemaps (and clears any stacked raster
overlays). Use the before_id input to control where each raster basemap is
inserted relative to existing layers.
Because that swap is destructive in stack mode, you can pass confirmStyleReplace
to confirm before the stacked rasters are discarded. It is only invoked in
allowMultiple mode when at least one raster basemap is currently stacked, and
receives the { basemap, replacedBasemapIds } it is about to replace. Return (or
resolve to) false to cancel and keep the current basemaps.
const control = new BasemapControl({
allowMultiple: true,
confirmStyleReplace: ({ basemap, replacedBasemapIds }) =>
window.confirm(
`Switching to "${basemap.name}" will remove ${replacedBasemapIds.length} stacked basemap(s). Continue?`,
),
});Methods
setBasemap(id)- Apply a basemap and remove the previous plugin-managed basemapaddBasemap(id)- Add a raster basemap as an additional overlay (style basemaps replace instead)removeBasemap(id)- Remove a previously added managed raster basemaptoggleBasemap(id)- Add the raster basemap if inactive, otherwise remove itisBasemapActive(id)- Whether the basemap is currently activesetMapTilerApiKey(apiKey)- Set or update the MapTiler API key used by MapTiler stylessetAmazonCredentials(apiKey, awsRegion)- Set or update Amazon Location credentialssetMapboxAccessToken(accessToken)- Set or update the Mapbox access tokensetProtomapsApiKey(apiKey)- Set or update the Protomaps API key used by Protomaps stylessetStadiaApiKey(apiKey)- Set or update the Stadia Maps API key used by Stadia basemapssetTomTomApiKey(apiKey)- Set or update the TomTom API key used by TomTom Traffic overlayssetHereApiKey(apiKey)- Set or update the HERE API key used by the HERE Traffic overlaysetGoogleMapsApiKey(apiKey)- Set or update the Google Maps API key used by the Google Traffic overlay and the base Google basemapsgetActiveBasemap()- Return the most recently selected basemap definitiongetActiveBasemaps()- Return all currently active basemap definitionsgetBasemaps()- Return the catalogsetBasemaps(basemaps)- Replace the catalogtoggle(),expand(),collapse()- Control panel visibilitygetState(),setState(state)- Read or update UI stateon(event, handler),off(event, handler)- Subscribe to eventsgetMap(),getContainer()- Access MapLibre/control internals
The panel includes a before_id input for raster basemap insertion. Leave it empty or set it to
none to add the basemap above existing layers, or enter a MapLibre layer id to insert the basemap
before that layer.
Events
basemapchange(includes amodeof'replace'or'add')basemapremoveerrorcollapseexpandstatechange
Attribution
Built-in basemaps include attribution strings in their MapLibre source definitions. Consumers are responsible for confirming that selected providers and usage volumes match their project requirements.
Development
npm install
npm run dev
npm test
npm run build
npm run build:examplesDocker
docker build -t maplibre-gl-basemap-control .
docker run -p 8080:80 maplibre-gl-basemap-controlOpen http://localhost:8080/maplibre-gl-basemap-control/ to view the examples.
