We spend days making sure that our maps are rendered quickly and beautifully, but that’s only part of what online maps are. Maps aren’t just a canvas for data: they’re also the zoom controls, scale indicators, search bars, and other user interface elements that make using the map possible. We just established the control API for Mapbox GL JS making it possible to write and distribute controls, and we want to invite everyone to tinker.

What’s a control?

Mapbox GL JS comes with four default controls:

  • Navigation: buttons for zooming and rotating the map
  • Geolocation: a button to find the user’s position
  • Scale: a display for pixel-to-meter scale
  • Attribution: a display for copyright and terms information

These are great examples of what controls can do: they can manipulate the map, like the navigation and geolocation controls do, or they can display information drawn from the map, as in the scale and attribution controls. They use the same plugin API we’ve just published so that you can implement similar controls and even more powerful ones.

Defined explicitly, a control:

  • Has an HTML element that ends up in the map, like a button or div
  • Has access to the map object, both to read information and manipulate it

This is only one kind of Mapbox GL JS add-on - there are also wrappers, like react-map-gl, and mapbox-gl-leaflet, and soon there’ll be custom source types, to support new formats. This is the first of multiple plugin APIs, and by no means the limit to Mapbox GL JS’s flexibility.

The control API

The control API is documented as an interface: the IControl interface. This means that a control can be any object as long as it implements two mandatory methods:

  • onAdd(map): element: called when a control is added to the map, with the map as the first argument. This method should return an element. The control can save a reference to the map, or use the map object to bind event listeners.
  • onRemove(map): called when a control is removed from the map, with the map as a first argument. This gives the control a chance to unbind event listeners, if it bound any in onAdd.

We designed this interface specifically so that you can implement plugins any way you want: you can use JavaScript’s new ES6 classes, or traditional prototype-based objects, or the module pattern. You also don’t need to use any part of the mapboxgl code to create plugins, so you can completely avoid any dependency conflict or complex peerDependency system.

Our documentation includes, inline, a minimum viable example of a plugin:

function HelloWorldControl() { }

HelloWorldControl.prototype.onAdd = function(map) {
    this._map = map;
    this._container = document.createElement('div');
    this._container.className = 'mapboxgl-ctrl';
    this._container.textContent = 'Hello, world';
    return this._container;
};

HelloWorldControl.prototype.onRemove() {
     this._container.parentNode.removeChild(this._container);
     this._map = undefined;
};

This control only adds a little ‘Hello, world’ text to the map, but you can use it as a starting point for your own.

Building an ecosystem

By establishing a stable API, we’re trying to create the basis for an ecosystem, and we’re already getting started. Here at Mapbox, we’ve written plugins for the Geocoding API, Directions API, and for drawing.