"Mapbox API" can mean two things. Here it means Mapbox GL JS, the browser library that draws an interactive map from a script tag. The Mapbox web services, such as geocoding, are separate HTTP endpoints, covered in the FAQ.
A map page needs three things: the library's script and CSS, a Mapbox access token, and a <div> with a size. Without the token the library loads and then refuses to draw.
Try that part first. This example loads Mapbox GL JS 3.32.0 from unpkg and asks for a map with no token.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mapbox GL JS without a token</title>
<link href="https://unpkg.com/mapbox-gl@3.32.0/dist/mapbox-gl.css" rel="stylesheet">
<script src="https://unpkg.com/mapbox-gl@3.32.0/dist/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
h1 { font-size: 17px; margin: 0 0 10px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
button { font: inherit; padding: 8px 14px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; cursor: pointer; }
#map { height: 120px; border-radius: 10px; background: #dbe4f0; margin-bottom: 10px; }
.box { background: #fff; border-radius: 10px; padding: 10px 12px; font-size: 14px; line-height: 1.5; margin-bottom: 8px; }
.ok { color: #0f5132; } .bad { color: #9a3412; }
code { font: 12.5px ui-monospace, Consolas, monospace; background: #eef1f5; padding: 0 3px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Mapbox GL JS: library vs. token</h1>
<div class="box" id="lib">Loading the library...</div>
<div class="row"><button id="go" type="button">Create a map with no token</button></div>
<div id="map"></div>
<div class="box" id="out">Press the button. The library is loaded, but no map can be drawn without a token.</div>
<script>
const lib = document.getElementById('lib');
const out = document.getElementById('out');
if (typeof mapboxgl === 'undefined') {
lib.innerHTML = '<span class="bad">The script did not load (blocked or offline).</span>';
} else {
lib.innerHTML = '<span class="ok">Library loaded.</span> <code>mapboxgl.version</code> = ' +
mapboxgl.version + ', <code>mapboxgl.supported()</code> = ' + mapboxgl.supported();
}
document.getElementById('go').addEventListener('click', () => {
if (typeof mapboxgl === 'undefined') return;
out.textContent = 'Creating the map...';
try {
const map = new mapboxgl.Map({
container: 'map',
// an inline style with no Mapbox-hosted data, and no accessToken on purpose
style: { version: 8, sources: {}, layers: [] },
center: [30, 15],
zoom: 1
});
map.on('error', (e) => {
out.innerHTML = '<span class="bad">error event:</span> ' + e.error.message;
});
} catch (err) {
out.innerHTML = '<span class="bad">thrown:</span> ' + err.message;
}
});
</script>
</body>
</html>
What a Mapbox page needs

This is the shape of the example in the Mapbox documentation, with a pinned version in the addresses:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.32.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.32.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = new mapboxgl.Map({
accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
container: 'map',
style: 'mapbox://styles/mapbox/standard',
zoom: 1,
center: [30, 15]
});
</script>
</body>
</html>
The library is also an npm package, so the same files are on unpkg under mapbox-gl@3.32.0/dist/.
Keep the library's script tag above your own script. A classic script runs as soon as the browser reaches it. Add a viewport meta tag so the map is sized correctly on a phone.
The access token
The accessToken option on the Map is what ties the map to your Mapbox account. Create the token in your account and paste it in place of the placeholder.
Mapbox has two kinds. A public token starts with pk. and is designed for web pages and apps. A secret token starts with sk. and is for servers only, so it never belongs in HTML.
On a public token you can add URL restrictions. The token then works only for requests that come from the addresses you list, and an unrestricted token works from any address.
The Mapbox pricing page lists up to 50,000 map loads a month for Mapbox GL JS. A map load is counted each time a Map object is initialized.
Panning and zooming afterwards are not counted again. So create one Map per page, not one per click.
Version 2 and later of the library is licensed under the Mapbox Terms of Service and needs an active Mapbox account. Versions 1.13 and earlier use the BSD-3-Clause licence.
Longitude first, then latitude
Mapbox GL JS writes a coordinate as [longitude, latitude], to match GeoJSON. Many map apps show the pair the other way round, so a copied number pair is a common source of a map in the wrong ocean.

The library checks latitude for you. Swap the numbers of point A below and watch the error. The page uses the library's LngLat class, which needs no token.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Longitude, latitude order</title>
<script src="https://unpkg.com/mapbox-gl@3.32.0/dist/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
h1 { font-size: 17px; margin: 0 0 4px; }
p.sub { margin: 0 0 12px; font-size: 13.5px; color: #4b5563; }
.grid { display: grid; grid-template-columns: auto 1fr 1fr; gap: 6px 8px; align-items: center; font-size: 14px; }
.grid b { font-weight: 600; }
input { width: 100%; box-sizing: border-box; font: inherit; padding: 7px 8px; border: 1px solid #cfd4dc; border-radius: 8px; }
.head { font-size: 12px; color: #6b7280; }
.row { display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0; }
button { font: inherit; padding: 8px 12px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; cursor: pointer; }
button.alt { background: #e5e7eb; color: #1d2330; }
.box { background: #fff; border-radius: 10px; padding: 10px 12px; font-size: 14px; line-height: 1.55; }
.ok { color: #0f5132; } .bad { color: #9a3412; }
code { font: 12.5px ui-monospace, Consolas, monospace; background: #eef1f5; padding: 0 3px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Coordinates are [longitude, latitude]</h1>
<p class="sub">Mapbox GL JS uses the GeoJSON order. Swap the two and see what the library says.</p>
<div class="grid">
<span></span><span class="head">longitude</span><span class="head">latitude</span>
<b>A</b><input id="aLng" value="127" inputmode="decimal" aria-label="A longitude"><input id="aLat" value="37.5" inputmode="decimal" aria-label="A latitude">
<b>B</b><input id="bLng" value="0" inputmode="decimal" aria-label="B longitude"><input id="bLat" value="0" inputmode="decimal" aria-label="B latitude">
</div>
<div class="row">
<button id="swap" type="button">Swap A's two numbers</button>
<button id="wrap" class="alt" type="button">Try A longitude 190</button>
<button id="reset" class="alt" type="button">Reset</button>
</div>
<div class="box" id="out"></div>
<script>
const $ = (id) => document.getElementById(id);
const out = $('out');
function show() {
if (typeof mapboxgl === 'undefined') { out.textContent = 'The library did not load.'; return; }
const lines = [];
try {
const a = new mapboxgl.LngLat(parseFloat($('aLng').value), parseFloat($('aLat').value));
const b = new mapboxgl.LngLat(parseFloat($('bLng').value), parseFloat($('bLat').value));
lines.push('<span class="ok">A =</span> <code>[' + a.toArray().join(', ') + ']</code>');
lines.push('A wrapped: <code>[' + a.wrap().toArray().join(', ') + ']</code>');
lines.push('Distance A to B: ' + Math.round(a.distanceTo(b) / 1000) + ' km (approximate)');
} catch (err) {
lines.push('<span class="bad">Error:</span> ' + err.message);
}
out.innerHTML = lines.join('<br>');
}
$('swap').addEventListener('click', () => {
const lng = $('aLng').value;
$('aLng').value = $('aLat').value;
$('aLat').value = lng;
show();
});
$('wrap').addEventListener('click', () => { $('aLng').value = '190'; $('aLat').value = '37.5'; show(); });
$('reset').addEventListener('click', () => {
$("aLng").value = "127"; $("aLat").value = "37.5";
$('bLng').value = '0'; $('bLat').value = '0';
show();
});
document.querySelectorAll('input').forEach((i) => i.addEventListener('input', show));
show();
</script>
</body>
</html>
The distance in the example is "approximate", in the words of the distanceTo documentation, and it is returned in metres.
Add a marker and a popup
After the Map exists, a marker is one chained call. Give it a position with setLngLat, then attach it with addTo.
const marker = new mapboxgl.Marker({ color: '#2563eb' })
.setLngLat([30.5, 50.5])
.addTo(map);
// bind a popup to the marker
marker.setPopup(new mapboxgl.Popup().setHTML('<b>Hello</b>'));
Marker takes options such as color and draggable. A Popup can also be placed on its own with setLngLat and addTo.
Build the whole page
This builder writes the complete file for you. Paste your own public token, choose the library address, and copy the result. A sk. token is refused.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mapbox page builder</title>
<style>
body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
h1 { font-size: 17px; margin: 0 0 4px; }
p.sub { margin: 0 0 12px; font-size: 13.5px; color: #4b5563; }
label { display: block; font-size: 12.5px; color: #4b5563; margin: 8px 0 3px; }
select { width: 100%; box-sizing: border-box; font: inherit; padding: 7px 8px; border: 1px solid #cfd4dc; border-radius: 8px; background: #fff; }
input { width: 100%; box-sizing: border-box; font: inherit; padding: 7px 8px; border: 1px solid #cfd4dc; border-radius: 8px; }
.three { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
#warn { min-height: 20px; font-size: 13px; margin-top: 6px; }
.ok { color: #0f5132; } .bad { color: #9a3412; }
textarea { width: 100%; box-sizing: border-box; height: 210px; margin-top: 10px; font: 12px/1.45 ui-monospace, Consolas, monospace; padding: 8px; border: 1px solid #cfd4dc; border-radius: 8px; resize: vertical; }
button { font: inherit; margin-top: 8px; padding: 8px 14px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; cursor: pointer; }
</style>
</head>
<body>
<h1>Build the single-file map page</h1>
<p class="sub">Fill in your own public token. The page text below updates as you type.</p>
<label for="tok">Your Mapbox public access token</label>
<input id="tok" placeholder="pk.your-token-here" autocomplete="off" spellcheck="false">
<div id="warn"></div>
<label for="cdn">Load the library from</label>
<select id="cdn">
<option value="mapbox">Mapbox's own CDN (api.mapbox.com)</option>
<option value="unpkg">unpkg (npm package, pinned 3.32.0)</option>
</select>
<div class="three">
<div><label for="lng">Longitude</label><input id="lng" value="30" inputmode="decimal"></div>
<div><label for="lat">Latitude</label><input id="lat" value="15" inputmode="decimal"></div>
<div><label for="zoom">Zoom</label><input id="zoom" value="1" inputmode="decimal"></div>
</div>
<textarea id="code" readonly aria-label="Generated page"></textarea>
<button id="copy" type="button">Copy the page</button>
<span id="msg" style="font-size:13px;margin-left:8px"></span>
<script>
const $ = (id) => document.getElementById(id);
function build() {
const token = $('tok').value.trim();
const w = $('warn');
if (token.startsWith('sk.')) {
w.innerHTML = '<span class="bad">That is a secret token (sk.). Never put it in a web page. Use a public pk. token.</span>';
} else if (token && !token.startsWith('pk.')) {
w.innerHTML = '<span class="bad">Public tokens start with pk.</span>';
} else {
w.innerHTML = token ? '<span class="ok">Looks like a public token.</span>' : '';
}
const safeToken = token.startsWith('sk.') ? 'YOUR_PUBLIC_TOKEN' : (token || 'YOUR_PUBLIC_TOKEN');
const base = $('cdn').value === 'unpkg'
? 'https://unpkg.com/mapbox-gl@3.32.0/dist/'
: 'https://api.mapbox.com/mapbox-gl-js/v3.32.0/';
const lng = parseFloat($('lng').value) || 0;
const lat = parseFloat($('lat').value) || 0;
const zoom = parseFloat($('zoom').value) || 0;
$('code').value = [
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="utf-8">',
'<meta name="viewport" content="width=device-width, initial-scale=1">',
'<link href="' + base + 'mapbox-gl.css" rel="stylesheet">',
'<script src="' + base + 'mapbox-gl.js"><\/script>',
'<style>',
' body { margin: 0; padding: 0; }',
' #map { position: absolute; top: 0; bottom: 0; width: 100%; }',
'</style>',
'</head>',
'<body>',
'<div id="map"></div>',
'<script>',
' const map = new mapboxgl.Map({',
' accessToken: ' + JSON.stringify(safeToken) + ',',
" container: 'map',",
" style: 'mapbox://styles/mapbox/standard',",
' center: [' + lng + ', ' + lat + '], // [longitude, latitude]',
' zoom: ' + zoom,
' });',
' new mapboxgl.Marker().setLngLat([' + lng + ', ' + lat + ']).addTo(map);',
'<\/script>',
'</body>',
'</html>'
].join('\n');
}
['tok', 'lng', 'lat', 'zoom'].forEach((id) => $(id).addEventListener('input', build));
$('cdn').addEventListener('change', build);
$('copy').addEventListener('click', () => {
const area = $('code');
area.select();
let done = false;
try { done = document.execCommand('copy'); } catch (e) {}
$('msg').textContent = done ? 'Copied.' : 'Selected. Press Ctrl+C (or long-press, Copy).';
});
build();
</script>
</body>
</html>
Where the map draws, and where it does not
The map is drawn in the browser, but its styles and tiles come from Mapbox's servers with your token attached. That is a request from your page to another site.

NOS runs the page and its scripts, and the scripts can come from unpkg. NOS blocks fetch to other sites, so a Mapbox-hosted style will not draw its tiles in a shared page.
Use a NOS link for what runs inside the page: the code, the coordinate checks, the layout.
For a map that needs no outside request, see map markers on an SVG map. To show a place with only a link, see HTML link to a map location.
When it does not work
| What you see | Cause | Fix |
|---|---|---|
| "A valid Mapbox access token is required" | No token, or a token the service rejects | Set accessToken to your public token |
| Blank area, no map | The container has no height | Give the div a height, or use the sizing from the example |
mapboxgl is not defined |
The script tag is missing, wrong, or placed after your code | Load the library first. See JavaScript not working |
| "Invalid LngLat latitude value" | Latitude and longitude are swapped | Write [longitude, latitude] |
| Works on your laptop, fails once published | The token has URL restrictions that omit the new address | Add the page address to the token's URL list |
A sk. token in the page |
A secret token was pasted | Use a public pk. token |
| Map stays empty in a NOS shared page | Requests to other sites are blocked | Test the live map on your own site |
Share it as a link
A map page is easier to show than to describe, and the people you send it to can drag and zoom it themselves. Sending the code as an attachment does not let them do that.
To share the page, paste it into a NOS document and choose Create share link. HTML to link walks through it.
The page renders as written and its scripts run, so the people you send it to can try it themselves. Anyone with the link can open it without an account. If you change the code later, the same link shows the new version.
Remember the limit from above. A shared NOS page cannot reach Mapbox's servers, so use it for the parts that run in the page, and open the finished map from your own site.