Skip to main content

Use Mapbox APIs in OpenLayers

OpenLayers is a full-featured open-source web mapping library. It supports raster XYZ tile sources, native MVT (Mapbox Vector Tile) rendering, and GeoJSON data layers. With the ol-mapbox-style adapter, it can also consume Mapbox Style JSON to render styled vector basemaps from Mapbox tilesets.

For full Mapbox ecosystem support, use Mapbox GL JS

Mapbox GL JS is the recommended library for building web maps with Mapbox. It provides first-class support for the full Mapbox ecosystem — including vector tile rendering, Mapbox Standard styles, Search JS, and Navigation SDKs — and is actively developed alongside Mapbox APIs.

That said, Mapbox's core Maps services use open standards: raster tile URLs follow the XYZ/TMS convention, vector tiles use the Mapbox Vector Tile specification, and Navigation and Search APIs return standard GeoJSON. This makes them interoperable with third-party mapping libraries and GIS software that support these formats.

Access tokens

All Mapbox APIs require an access token for authentication. Your token is passed as the access_token query parameter on every request.

You can find your default public token — or create a new one with specific scopes — on the Access tokens page in the Mapbox Developer Console. Public tokens start with pk..

https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/512/{z}/{x}/{y}@2x?access_token=YOUR_ACCESS_TOKEN

When using Mapbox APIs in a client-side web application, use a public token with URL restrictions configured to your domain, so the token can't be used by other sites. See How to use Mapbox securely for guidance on restricting tokens and avoiding exposure.

One key difference from Leaflet: OpenLayers renders internally in Web Mercator (EPSG:3857), so coordinates from external sources (WGS84 / EPSG:4326) must be reprojected. Use ol.proj.fromLonLat([lng, lat]) to convert when creating geometries, and pass featureProjection: 'EPSG:3857' when reading GeoJSON features.

Raster basemap tiles

The Static Tiles API renders any Mapbox style as raster tiles. Add them using an XYZ source inside a Tile layer.

new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/512/{z}/{x}/{y}@2x?access_token=YOUR_MAPBOX_ACCESS_TOKEN',
tileSize: 512,
maxZoom: 22,
attributions:
'© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> ' +
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
})
})

Satellite imagery

The Raster Tiles API serves the mapbox.satellite imagery tileset as standard XYZ tiles. These are 256px tiles with no labels or road overlays.

new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.jpg90?access_token=YOUR_MAPBOX_ACCESS_TOKEN',
maxZoom: 22,
attributions:
'© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> ' +
'© <a href="https://www.maxar.com/">Maxar</a>'
})
})

To display satellite imagery with road and label overlays, use the satellite-streets-v12 style through the Static Tiles API instead:

https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/512/{z}/{x}/{y}@2x?access_token=YOUR_MAPBOX_ACCESS_TOKEN

Vector tiles with custom style

OpenLayers can render Mapbox Vector Tiles natively, and the ol-mapbox-style library (olms) translates Mapbox Style JSON into OpenLayers rendering rules. This lets you define your own visual style over the mapbox.mapbox-streets-v8 tileset — the same vector data that powers Mapbox's hosted styles.

<script src="https://cdn.jsdelivr.net/npm/ol@10/dist/ol.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ol-mapbox-style@12/dist/olms.js"></script>

Create the map without any layers, then call olms.apply() with a style JSON object that references the streets-v8 tile URL directly:

const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';

const map = new ol.Map({
target: 'map',
view: new ol.View({ center: ol.proj.fromLonLat([-74.006, 40.7128]), zoom: 13 })
// No layers — olms.apply() adds them from the style object
});

const customStyle = {
version: 8,
glyphs: `https://api.mapbox.com/fonts/v1/mapbox/{fontstack}/{range}.pbf?access_token=${accessToken}`,
sources: {
'mapbox-streets': {
type: 'vector',
tiles: [`https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.mvt?access_token=${accessToken}`],
maxzoom: 16
}
},
layers: [
{ id: 'background', type: 'background', paint: { 'background-color': '#1a1a2e' } },
{ id: 'water', type: 'fill', source: 'mapbox-streets', 'source-layer': 'water',
paint: { 'fill-color': '#16213e' } },
{ id: 'road-major', type: 'line', source: 'mapbox-streets', 'source-layer': 'road',
filter: ['in', 'class', 'primary', 'secondary', 'trunk'],
paint: { 'line-color': '#445577', 'line-width': 2 } },
{ id: 'place-label', type: 'symbol', source: 'mapbox-streets', 'source-layer': 'place_label',
layout: { 'text-field': ['get', 'name'], 'text-font': ['Open Sans Regular'], 'text-size': 12 },
paint: { 'text-color': '#a0a0cc', 'text-halo-color': '#1a1a2e', 'text-halo-width': 1 } }
// add more layers to style buildings, parks, roads, etc.
]
};

olms.apply(map, customStyle);

The layer definitions reference source-layer names from the streets-v8 tileset schema. You can add as many layers as needed to achieve your desired visual style.

olms.apply() returns a Promise — chain .then() to add OpenLayers vector layers on top of the basemap after it loads:

olms.apply(map, customStyle).then(() => {
map.addLayer(new ol.layer.Vector({
source: new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures(myGeoJson, {
featureProjection: 'EPSG:3857'
})
})
}));
});

The Directions API and Isochrone API return GeoJSON. Use ol.format.GeoJSON to parse the responses into OpenLayers features, then add them to a Vector layer. Always pass featureProjection: 'EPSG:3857' so OpenLayers reprojects the WGS84 coordinates automatically.

const geoJsonFormat = new ol.format.GeoJSON();
const vectorSource = new ol.source.Vector();
map.addLayer(new ol.layer.Vector({ source: vectorSource }));

// Isochrones
const isoRes = await fetch(
`https://api.mapbox.com/isochrone/v1/mapbox/driving/${origin[0]},${origin[1]}` +
`?contours_minutes=5,10,15&polygons=true&access_token=YOUR_MAPBOX_ACCESS_TOKEN`
);
const isoData = await isoRes.json();
const colors = ['#1d4ed8', '#3b82f6', '#93c5fd'];

isoData.features.reverse().forEach((feature, i) => {
const olFeatures = geoJsonFormat.readFeatures(feature, { featureProjection: 'EPSG:3857' });
olFeatures.forEach(f => f.setStyle(new ol.style.Style({
fill: new ol.style.Fill({ color: colors[i] + '40' }),
stroke: new ol.style.Stroke({ color: colors[i], width: 1.5 })
})));
vectorSource.addFeatures(olFeatures);
});

// Route
const routeRes = await fetch(
`https://api.mapbox.com/directions/v5/mapbox/driving/` +
`${origin.join(',')};${destination.join(',')}` +
`?geometries=geojson&access_token=YOUR_MAPBOX_ACCESS_TOKEN`
);
const routeData = await routeRes.json();

const routeFeature = geoJsonFormat.readFeature(routeData.routes[0].geometry, {
featureProjection: 'EPSG:3857'
});
routeFeature.setStyle(new ol.style.Style({
stroke: new ol.style.Stroke({ color: '#1d4ed8', width: 4 })
}));
vectorSource.addFeature(routeFeature);

Mapbox Search JS provides MapboxSearchBox, a web component that fires a retrieve event when a user selects a result. Position it as an overlay on the map and use ol.proj.fromLonLat to convert the result coordinates before updating the view.

<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10/ol.css" />
<script src="https://cdn.jsdelivr.net/npm/ol@10/dist/ol.js"></script>
<script id="search-js" defer
src="https://api.mapbox.com/search-js/v1.2.0/web.js">
</script>
<style>
#map { position: absolute; top: 0; bottom: 0; width: 100%; }

#search-container {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
width: 320px;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="search-container"></div>
<script>
const ACCESS_TOKEN = 'YOUR_MAPBOX_ACCESS_TOKEN';

const markerSource = new ol.source.Vector();
const markerLayer = new ol.layer.Vector({
source: markerSource,
style: new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/images/marker-icon.png'
})
})
});

const map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.XYZ({
url:
'https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/512/{z}/{x}/{y}@2x?access_token=' +
ACCESS_TOKEN,
tileSize: 512,
maxZoom: 22,
attributions:
'© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> ' +
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
})
}),
markerLayer
],
view: new ol.View({
center: ol.proj.fromLonLat([-74.006, 40.7128]),
zoom: 12
})
});

window.addEventListener('load', () => {
const searchBox = new MapboxSearchBox();
searchBox.accessToken = ACCESS_TOKEN;
searchBox.options = { types: 'address,poi', proximity: [-74.006, 40.7128] };
document.getElementById('search-container').appendChild(searchBox);

searchBox.addEventListener('retrieve', (event) => {
const feature = event.detail.features[0];
const [lng, lat] = feature.geometry.coordinates;
const coords = ol.proj.fromLonLat([lng, lat]); // reproject for OpenLayers

map.getView().animate({ center: coords, zoom: 15, duration: 600 });

markerSource.clear();
markerSource.addFeature(new ol.Feature(new ol.geom.Point(coords)));
});
});
</script>
</body>

Pricing

Most Mapbox services include a free tier that covers a generous volume of monthly usage — enough for development and many production use cases. Beyond the free tier, pricing is usage-based and varies by product.

ServicePricing details
Maps (raster and vector tiles)Maps pricing
Directions APINavigation pricing
Isochrone APINavigation pricing
Search / GeocodingSearch pricing

See the Mapbox pricing page for current free tier limits and rates. When consuming tiles through a third-party library (rather than Mapbox GL JS), tile requests are billed individually under the Maps API rather than being bundled into a map load.

Was this page helpful?