npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

maplibre-gl-basemap-control

v0.13.0

Published

A MapLibre GL JS control for searching and switching public basemaps

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.

npm version License: MIT

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 IControl implementation
  • 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-gl

Vanilla 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: '&copy; 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. Set stadiaApiKey or 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.com tiles 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 a googleMapsApiKey so 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 attribution on 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 basemap
  • addBasemap(id) - Add a raster basemap as an additional overlay (style basemaps replace instead)
  • removeBasemap(id) - Remove a previously added managed raster basemap
  • toggleBasemap(id) - Add the raster basemap if inactive, otherwise remove it
  • isBasemapActive(id) - Whether the basemap is currently active
  • setMapTilerApiKey(apiKey) - Set or update the MapTiler API key used by MapTiler styles
  • setAmazonCredentials(apiKey, awsRegion) - Set or update Amazon Location credentials
  • setMapboxAccessToken(accessToken) - Set or update the Mapbox access token
  • setProtomapsApiKey(apiKey) - Set or update the Protomaps API key used by Protomaps styles
  • setStadiaApiKey(apiKey) - Set or update the Stadia Maps API key used by Stadia basemaps
  • setTomTomApiKey(apiKey) - Set or update the TomTom API key used by TomTom Traffic overlays
  • setHereApiKey(apiKey) - Set or update the HERE API key used by the HERE Traffic overlay
  • setGoogleMapsApiKey(apiKey) - Set or update the Google Maps API key used by the Google Traffic overlay and the base Google basemaps
  • getActiveBasemap() - Return the most recently selected basemap definition
  • getActiveBasemaps() - Return all currently active basemap definitions
  • getBasemaps() - Return the catalog
  • setBasemaps(basemaps) - Replace the catalog
  • toggle(), expand(), collapse() - Control panel visibility
  • getState(), setState(state) - Read or update UI state
  • on(event, handler), off(event, handler) - Subscribe to events
  • getMap(), 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 a mode of 'replace' or 'add')
  • basemapremove
  • error
  • collapse
  • expand
  • statechange

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:examples

Docker

docker build -t maplibre-gl-basemap-control .
docker run -p 8080:80 maplibre-gl-basemap-control

Open http://localhost:8080/maplibre-gl-basemap-control/ to view the examples.