Skip to content
Migrate from Mapbox GL JS

Migrate a Mapbox GL JS Map to Maptoolkit Maps JS

Most of a Mapbox GL JS application does not change when it moves to Maps JS, because Maps JS is MapLibre GL JS with the Maptoolkit services wired in, and MapLibre is the open fork of Mapbox GL JS v1. The map below is the finished port, running. Under it is the complete diff against the Mapbox GL JS original, so you can see exactly how little of it changed.

The original is not embedded beside it, because Mapbox GL JS v2 and later refuses to start without a Mapbox access token: a runnable “before” would mean asking you to supply one and be billed by Mapbox to look at a migration guide. The diff is the honest substitute.

const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    // Everything below this line is unchanged from the Mapbox GL JS original.
    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    new maptoolkit.Marker({ color: '#c0392b' })
        .setLngLat([11.39085, 47.27574])
        .setPopup(new maptoolkit.Popup().setHTML('<strong>Innsbruck</strong>'))
        .addTo(map);

    map.on('load', () => {
        map.addSource('trail', {
            type: 'geojson',
            data: {
                type: 'Feature',
                properties: {},
                geometry: {
                    type: 'LineString',
                    coordinates: [[11.3857, 47.2683], [11.3950, 47.2760], [11.4100, 47.2830], [11.4300, 47.2900]]
                }
            }
        });

        map.addLayer({
            id: 'trail',
            type: 'line',
            source: 'trail',
            layout: { 'line-join': 'round', 'line-cap': 'round' },
            paint: { 'line-color': '#2a3561', 'line-width': 5 }
        });

        map.addSource('stops', {
            type: 'geojson',
            data: {
                type: 'FeatureCollection',
                features: [
                    { type: 'Feature', properties: { size: 3 }, geometry: { type: 'Point', coordinates: [11.3857, 47.2683] } },
                    { type: 'Feature', properties: { size: 7 }, geometry: { type: 'Point', coordinates: [11.4100, 47.2830] } },
                    { type: 'Feature', properties: { size: 5 }, geometry: { type: 'Point', coordinates: [11.4300, 47.2900] } }
                ]
            }
        });

        map.addLayer({
            id: 'stops',
            type: 'circle',
            source: 'stops',
            paint: {
                'circle-radius': ['interpolate', ['linear'], ['get', 'size'], 1, 5, 10, 16],
                'circle-color': '#e8710a',
                'circle-stroke-color': '#fff',
                'circle-stroke-width': 2
            }
        });

        map.on('click', 'stops', (e) => {
            new maptoolkit.Popup()
                .setLngLat(e.features[0].geometry.coordinates)
                .setHTML(`Size ${e.features[0].properties.size}`)
                .addTo(map);
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Migrated from Mapbox GL JS - Maptoolkit Maps JS</title>
    <meta property="og:description" content="A Mapbox GL JS map ported to Maps JS." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.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>
    const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    // Everything below this line is unchanged from the Mapbox GL JS original.
    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    new maptoolkit.Marker({ color: '#c0392b' })
        .setLngLat([11.39085, 47.27574])
        .setPopup(new maptoolkit.Popup().setHTML('<strong>Innsbruck</strong>'))
        .addTo(map);

    map.on('load', () => {
        map.addSource('trail', {
            type: 'geojson',
            data: {
                type: 'Feature',
                properties: {},
                geometry: {
                    type: 'LineString',
                    coordinates: [[11.3857, 47.2683], [11.3950, 47.2760], [11.4100, 47.2830], [11.4300, 47.2900]]
                }
            }
        });

        map.addLayer({
            id: 'trail',
            type: 'line',
            source: 'trail',
            layout: { 'line-join': 'round', 'line-cap': 'round' },
            paint: { 'line-color': '#2a3561', 'line-width': 5 }
        });

        map.addSource('stops', {
            type: 'geojson',
            data: {
                type: 'FeatureCollection',
                features: [
                    { type: 'Feature', properties: { size: 3 }, geometry: { type: 'Point', coordinates: [11.3857, 47.2683] } },
                    { type: 'Feature', properties: { size: 7 }, geometry: { type: 'Point', coordinates: [11.4100, 47.2830] } },
                    { type: 'Feature', properties: { size: 5 }, geometry: { type: 'Point', coordinates: [11.4300, 47.2900] } }
                ]
            }
        });

        map.addLayer({
            id: 'stops',
            type: 'circle',
            source: 'stops',
            paint: {
                'circle-radius': ['interpolate', ['linear'], ['get', 'size'], 1, 5, 10, 16],
                'circle-color': '#e8710a',
                'circle-stroke-color': '#fff',
                'circle-stroke-width': 2
            }
        });

        map.on('click', 'stops', (e) => {
            new maptoolkit.Popup()
                .setLngLat(e.features[0].geometry.coordinates)
                .setHTML(`Size ${e.features[0].properties.size}`)
                .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. Take a Mapbox GL JS map with a marker, a popup, a GeoJSON line layer and a data-driven circle layer, and port it to Maptoolkit Maps JS.

The complete diff

Four lines change in the setup, and the namespace changes throughout. Nothing else does.

- <script src="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
- <link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css" />
+ <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
+ <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />

- mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
- const map = new mapboxgl.Map({
+ const map = new maptoolkit.Map({
      container: 'map',
+     apiKey: API_KEY,
-     style: 'mapbox://styles/mapbox/outdoors-v12',
+     style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
      center: [11.39085, 47.27574],
      zoom: 12
  });

- map.addControl(new mapboxgl.NavigationControl(), 'top-right');
+ map.addControl(new maptoolkit.NavigationControl(), 'top-right');

- new mapboxgl.Marker({ color: '#c0392b' })
+ new maptoolkit.Marker({ color: '#c0392b' })
      .setLngLat([11.39085, 47.27574])
-     .setPopup(new mapboxgl.Popup().setHTML('<strong>Innsbruck</strong>'))
+     .setPopup(new maptoolkit.Popup().setHTML('<strong>Innsbruck</strong>'))
      .addTo(map);

Every addSource, addLayer, paint property, expression and event handler in the example above is byte for byte what it was on Mapbox GL JS.

How it works

The namespace is the bulk of the change, and it is mechanical. mapboxgl. becomes maptoolkit. for Map, Marker, Popup, LngLat, LngLatBounds, MercatorCoordinate, NavigationControl, ScaleControl and the rest. A find and replace on mapboxgl. handles a codebase in one pass.

The access token becomes two things, not one. mapboxgl.accessToken was a global; the Maptoolkit key is an apiKey constructor option and a query parameter on the style URL. Setting only the constructor option leaves the style URL unauthenticated and the map loads blank; setting only the style URL leaves the SDK’s own service calls unauthenticated. Set both, from one constant.

mapbox:// URLs have no equivalent. Styles, sprites, glyphs and tile sources all have to become real HTTPS URLs. That is usually the only part of a migration that takes real work, because a custom Mapbox Studio style also has to be rebuilt: the layers reference the Mapbox Streets schema, and the field names differ from ours. The Schema Reference is what you remap against.

The expression language is identical, so interpolate, step, match, case, feature-state and the rest all carry over untouched. This is the part people expect to lose and do not.

What Maps JS adds beyond plain MapLibre is the reason to pick it over the fork: the StyleControl, TerrainControl and IsochroneControl are one line each rather than a fetch and a layer, and the key is handled for the SDK’s own service calls.

Watch terrain specifically. Maptoolkit DEM tiles are Terrarium encoded and Mapbox uses terrain-rgb. A copied raster-dem source block loads without an error and renders plausible but wrong elevation, which is the single most expensive thing to miss in this migration.

Next steps

The migration guide covers the parts that are not code: which Mapbox services map to which of ours, what has no equivalent at all, and how tile and API requests are metered differently.

Once the map is running, the controls are what justify the move. A style switcher and 3D terrain are a line each, and the same API key already authorizes the Routing API, Geocoding API and Isochrone API.