Skip to content
Documentation menu
docs / migrate-from-mapbox · raw .md

Migrating from Mapbox

Most of a Mapbox GL JS map moves across with the renderer swapped and the URLs changed, because MapMap renders with MapLibre GL JS, which began as a fork of Mapbox GL JS and still shares its style specification and most of its API. addSource, addLayer, expressions, queryRenderedFeatures and feature-state are the same calls with the same arguments. What changes is narrow and knowable: the style URL, the mapbox:// source URLs, the newer Mapbox-only conveniences (slots, interactions, Standard style config) and where your hosted tilesets live. This page is that list, gaps included. Routing, geocoding and the other APIs are covered in migrating from Mapbox: what changes and what does not.

Keys

A Mapbox pk. token becomes a MapMap snk_ key, issued by one unauthenticated call with no card:

sh
curl -fsS -X POST "https://api.mapmap.ai/v1/keys" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "accept_tos": true}'

createMap takes it as apiKey. For URL-only contexts (a style URL handed to a native SDK) it travels as ?api_key=snk_….

The map: style URL to createMap

ts
// Before
import mapboxgl from "mapbox-gl";
mapboxgl.accessToken = "pk.…";
const map = new mapboxgl.Map({
  container: "map",
  style: "mapbox://styles/mapbox/streets-v12",
  center: [-0.1278, 51.5074],
  zoom: 12,
});

// After
import { createMap } from "@mapmap/maps";
import "maplibre-gl/dist/maplibre-gl.css";

const map = createMap({
  container: "map",
  apiKey: "snk_…",
  style: "light",            // "light" | "dark" | a Studio theme | a style URL
  center: [-0.1278, 51.5074],
  zoom: 12,
});
await map.whenReady();
sh
npm install @mapmap/maps maplibre-gl pmtiles

map.map is the underlying MapLibre map. Everything you used to call on your Mapbox map object you now call on map.map; the SDK's own helpers (RouteLayer, PlacesLayer, Geocoder and the rest) sit beside it. If you construct a maplibregl.Map yourself instead, import MapLibre as import * as maplibregl from "maplibre-gl": MapLibre 6 has no default export.

The container needs an explicit height, exactly as before.

Sources: mapbox:// to pmtiles:// or your own tiles

Every mapbox:// URL in a style or an addSource call has to change, because it resolves only against Mapbox's servers.

MapboxMapMap
mapbox://styles/mapbox/streets-v12 and the other Mapbox stylesstyle: "light" or "dark", a Studio theme, or a hosted style URL from POST /styles
mapbox://mapbox.mapbox-streets-v8 (the basemap vector tiles)Comes with the style. To reference it yourself: GET /tiles/{territory}/tiles.json. The schema is OpenMapTiles, so layer and property names differ from Mapbox Streets
mapbox://your-user.your-tileset (an uploaded tileset)A PMTiles file on your own bucket: url: "pmtiles://https://your-bucket.example.com/your-data.pmtiles". See Bring your own tiles
mapbox://mapbox.satelliteYour own imagery as an XYZ raster source. MapMap does not offer a hosted satellite tileset for your apps
mapbox://mapbox.mapbox-terrain-dem-v1terrain: true on createMap, which drapes the map over the same DEM the gateway answers POST /elevation from
GeoJSON, image, video and raster sources with https URLsUnchanged

Because the basemap schema is OpenMapTiles rather than Mapbox Streets, a filter written against Streets property names (for example class values or maki icons) needs checking against the styleable layer ids. Your own data layers are unaffected.

addLayer(..., beforeId) and slots

Mapbox Standard puts your layers into named slots (bottom, middle, top). MapLibre has no slots; you pass the id of the layer to insert beneath, which is what Mapbox did before Standard and still accepts. The MapMap styles share one layer skeleton, so the three slots map to three fixed layer ids, listed in the insertion points table:

ts
// Mapbox Standard
map.addLayer({ id: "zones", type: "fill", source: "zones", slot: "middle" });

// MapMap today
map.map.addLayer({ id: "zones", type: "fill", source: "zones" }, "road-oneway");

Slots arrive in the SDK 0.17 under the same names: map.slot("middle") returns the layer id to pass, resolved against the live style so a Studio theme that drops a layer still works.

addInteraction and featuresets

map.addInteraction and the Standard featuresets (poi, place-labels, buildings) are Mapbox only. On MapLibre the same behaviour is the older pattern, which Mapbox still supports too: layer-scoped events plus feature-state.

ts
// Mapbox
map.addInteraction("zone-click", {
  type: "click",
  target: { layerId: "zones" },
  handler: (e) => console.log(e.feature),
});

// MapMap today
map.map.on("click", "zones", (e) => console.log(e.features?.[0]));

The SDK adds an addInteraction helper in 0.17, shaped like Mapbox's, with the poi, place-labels and buildings featureset ids mapped onto the MapMap layers. Until then, hover highlight with feature-state and click a feature for a popup are the patterns to copy.

What does not change

  • queryRenderedFeatures and querySourceFeatures: same signatures, same results shape.
  • feature-state: setFeatureState, getFeatureState, removeFeatureState, promoteId and the ["feature-state", …] expression are the same. One difference to plan for: MapMap basemap features carry tile-assigned ids, not OpenStreetMap ids, so state on basemap layers is per tile. Your own sources behave as before.
  • Expressions and data-driven styling, clustering with clusterProperties, heatmaps, fill-extrusion, line-gradient with lineMetrics, image and video sources, custom WebGL layers, terrain and the globe projection.
  • Popups and markers: new maplibregl.Popup() and new maplibregl.Marker() replace their mapboxgl namesakes one for one.

The examples gallery has fifteen runnable data-layer samples written against exactly this surface.

Standard style config to Studio themes and global-state

Mapbox Standard exposes runtime config (setConfigProperty("basemap", "lightPreset", "dusk"), show or hide POI labels, theme colours). On MapMap the design lives in a Studio theme: 19 palette slots, per-layer overrides, POI and marker designs, saved as a theme document and published as a hosted style. Switch between themes at run time with setStyle, or let ThemeScheduler swap day and night on sun times.

For your own layers, MapLibre's global-state expression is the closest thing to Mapbox's config expression: declare a value once, read it in any paint or filter expression with ["global-state", "name"], and change it with map.map.setGlobalStateProperty("name", value).

What MapLibre does not have

Stated plainly, so a gap does not surface halfway through a migration. Raster-array sources and the raster particle layer, model layers (model and 3D landmarks), the snow and rain layers, multiple lights and style imports are not available, and we make no promise that they will be. There is no addInteraction and no featuresets in MapLibre itself (the SDK helper above covers the common case). Video sources are MapLibre GL JS only: MapLibre Native on iOS and Android has none. Going the other way, MapLibre has a color-relief layer for colouring elevation, which Mapbox does not.

Studio uploads to your own hosting

Mapbox Studio uploads a dataset, tiles it on Mapbox's servers and hosts the tileset. MapMap has no hosted tileset upload, by decision: we do not hold customer datasets. The route is bring your own hosting:

  1. Tile on your own machine. The self-host distro ships snfactory tile your-data.geojson -o your-data.pmtiles --layer your-data; any PMTiles writer works.
  2. Put the file on your own bucket (S3, R2, GCS, Azure) with range requests and CORS on.
  3. Reference it as pmtiles://https://… in your style or an addSource call.

Small datasets need none of that: a GeoJSON URL in an addSource call is fetched by the browser directly. Point datasets you want searchable (a store finder, places along a route) go to PUT /places, up to 10,000 per key. The full walk-through is on add your own data.

Pricing

From /pricing.json, which is always the source of truth over this page. Prices are in pounds sterling and prepaid: a key stops at its limit rather than running up an invoice.

  • Free tier: 50,000 calls a month on an email-verified key, no card, commercial use allowed. A new key works immediately for 1,000 calls before verification.
  • Map tiles are discounted: one call per 50 vector-tile requests, and hosted style reads are public and unmetered.
  • Standard calls (geocoding, car and other non-truck routing, matrix, isochrones) are 0.05p each from call 50,001 to 1 million, 0.03p to 10 million and 0.02p beyond.
  • Monthly plans: Starter £49 (250,000 calls), Growth £299 (2 million), Scale £1,250 (10 million).
  • Tiles you host yourself cost nothing on MapMap: a PMTiles file on your bucket is read by the browser straight from your storage and never passes through the gateway.

Next steps