Migration guide for developers moving from Google Maps Platform to Mapbox GL JS, covering API equivalents, pattern translations, and key differences
npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-google-maps-migration
Comprehensive guidance for migrating from Google Maps Platform to Mapbox GL JS. Provides API equivalents, pattern translations, and strategies for successful migration.
.setMap(map)Key Insight: Mapbox treats everything as data + styling, not individual objects.
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.7749, lng: -122.4194 },
zoom: 12,
mapTypeId: 'roadmap' // or 'satellite', 'hybrid', 'terrain'
});
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12', // or satellite-v9, outdoors-v12
center: [-122.4194, 37.7749], // [lng, lat] - note the order!
zoom: 12
});
Key Differences:
{lat, lng}, Mapbox uses [lng, lat]| Google Maps | Mapbox GL JS | Notes |
| ------------------------ | -------------------------------------- | ----------------------------- |
| map.setCenter(latLng) | map.setCenter([lng, lat]) | Coordinate order reversed |
| map.getCenter() | map.getCenter() | Returns LngLat object |
| map.setZoom(zoom) | map.setZoom(zoom) | Same behavior |
| map.getZoom() | map.getZoom() | Same behavior |
| map.panTo(latLng) | map.panTo([lng, lat]) | Animated pan |
| map.fitBounds(bounds) | map.fitBounds([[lng,lat],[lng,lat]]) | Different bound format |
| map.setMapTypeId(type) | map.setStyle(styleUrl) | Completely different approach |
| map.getBounds() | map.getBounds() | Similar |
| Google Maps | Mapbox GL JS | Notes |
| ------------------------------------------------- | ---------------------- | --------------------- |
| google.maps.event.addListener(map, 'click', fn) | map.on('click', fn) | Simpler syntax |
| event.latLng | event.lngLat | Event property name |
| 'center_changed' | 'move' / 'moveend' | Different event names |
| 'zoom_changed' | 'zoom' / 'zoomend' | Different event names |
| 'bounds_changed' | 'moveend' | No direct equivalent |
| 'mousemove' | 'mousemove' | Same |
| 'mouseout' | 'mouseleave' | Different name |
Google Maps:
const marker = new google.maps.Marker({
position: { lat: 37.7749, lng: -122.4194 },
map: map,
title: 'San Francisco',
icon: 'custom-icon.png'
});
// Remove marker
marker.setMap(null);
Mapbox GL JS:
// Create marker
const marker = new mapboxgl.Marker()
.setLngLat([-122.4194, 37.7749])
.setPopup(new mapboxgl.Popup().setText('San Francisco'))
.addTo(map);
// Remove marker
marker.remove();
Google Maps:
const markers = locations.map(
(loc) =>
new google.maps.Marker({
position: { lat: loc.lat, lng: loc.lng },
map: map
})
);
Mapbox GL JS (Equivalent Approach):
// Same object-oriented approach
const markers = locations.map((loc) => new mapboxgl.Marker().setLngLat([loc.lng, loc.lat]).addTo(map));
Mapbox GL JS (Data-Driven Approach - Recommended for 100+ points):
// Add as GeoJSON source + layer (uses WebGL, not DOM)
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: locations.map((loc) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [loc.lng, loc.lat] },
properties: { name: loc.name }
}))
}
});
map.addLayer({
id: 'points-layer',
type: 'circle', // or 'symbol' for icons
source: 'points',
paint: {
'circle-radius': 8,
'circle-color': '#ff0000'
}
});
Performance Advantage: Google Maps renders all markers as DOM elements (even when using the Data Layer), which becomes slow with 500+ markers. Mapbox's circle and symbol layers are rendered by WebGL, making them much faster for large datasets (1,000-10,000+ points). This is a significant advantage when building applications with many points.
const infowindow = new google.maps.InfoWindow({
content: '<h3>Title</h3><p>Content</p>'
});
marker.addListener('click', () => {
infowindow.open(map, marker);
});
// Option 1: Attach to marker
const marker = new mapboxgl.Marker()
.setLngLat([-122.4194, 37.7749])
.setPopup(new mapboxgl.Popup().setHTML('<h3>Title</h3><p>Content</p>'))
.addTo(map);
// Option 2: On layer click (for data-driven markers)
map.on('click', 'points-layer', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const description = e.features[0].properties.description;
new mapboxgl.Popup().setLngLat(coordinates).setHTML(description).addTo(map);
});
Identify all Google Maps features you use:
<!-- Replace Google Maps script -->
<script src="https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css" rel="stylesheet" />
Start with basic map initialization:
new google.maps.Map() with new mapboxgl.Map()Prioritize by complexity:
Change event syntax:
google.maps.event.addListener() -> map.on()latLng -> lngLat)Take advantage of Mapbox features:
// Google Maps
{ lat: 37.7749, lng: -122.4194 }
// Mapbox (REVERSED!)
[-122.4194, 37.7749]
Always double-check coordinate order!
// Google Maps
map.on('click', (e) => {
console.log(e.latLng.lat(), e.latLng.lng());
});
// Mapbox
map.on('click', (e) => {
console.log(e.lngLat.lat, e.lngLat.lng);
});
// Google Maps - immediate
const marker = new google.maps.Marker({ map: map });
// Mapbox - wait for load
map.on('load', () => {
map.addSource(...);
map.addLayer(...);
});
// Google Maps
marker.setMap(null);
// Mapbox - must remove both
map.removeLayer('layer-id');
map.removeSource('source-id');
Never remove and re-add layers to update data — this reinitializes WebGL resources and causes a visible flash. Instead:
// ✅ Update data in place (no flash)
map.getSource('stores').setData(newGeoJSON);
// ✅ Filter existing data (GPU-side, fastest)
map.setFilter('stores-layer', ['==', ['get', 'category'], 'coffee']);
// ❌ BAD: remove + re-add causes flash
map.removeLayer('stores-layer');
map.removeSource('stores');
map.addSource('stores', { ... });
map.addLayer({ ... });
Consider staying with Google Maps if:
// GOOGLE MAPS
const map = new google.maps.Map(el, {
center: { lat: 37.7749, lng: -122.4194 },
zoom: 12
});
const marker = new google.maps.Marker({
position: { lat: 37.7749, lng: -122.4194 },
map: map
});
google.maps.event.addListener(map, 'click', (e) => {
console.log(e.latLng.lat(), e.latLng.lng());
});
// MAPBOX GL JS
mapboxgl.accessToken = 'YOUR_TOKEN';
const map = new mapboxgl.Map({
container: el,
center: [-122.4194, 37.7749], // REVERSED!
zoom: 12,
style: 'mapbox://styles/mapbox/streets-v12'
});
const marker = new mapboxgl.Marker()
.setLngLat([-122.4194, 37.7749]) // REVERSED!
.addTo(map);
map.on('click', (e) => {
console.log(e.lngLat.lat, e.lngLat.lng);
});
Remember: lng, lat order in Mapbox!
Works with:
The following reference files contain detailed migration guides for specific topics. Load them when working on those areas:
references/shapes-geocoding.md — Polygons, Polylines, Custom Icons, Geocodingreferences/directions-controls.md — Directions/Routing, Controlsreferences/clustering-styling.md — Clustering, Styling/Appearancereferences/data-performance.md — Data Updates, Performance, Common Migration Patterns (Store Locator, Drawing Tools, Heatmaps)references/api-services.md — API Services Comparison, Pricing, Plugins, Framework Integration, Testing, Migration ChecklistTo load a reference, read the file relative to this skill directory, e.g.:
Load references/shapes-geocoding.md
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when the user asks to run Codex CLI (codex exec, codex resume) or references OpenAI Codex for code analysis, refactoring, or automated editing. Uses GPT-5.2 by default for state-of-the-art software engineering.
Implement memory-safe programming with RAII, ownership, smart pointers, and resource management across Rust, C++, and C. Use when writing safe systems code, managing resources, or preventing memory bugs.
Python/HTSlib workflows for genomic files. Use when reading, querying, filtering, or writing SAM/BAM/CRAM, VCF/BCF, FASTA/FASTQ, or tabix data with pysam, including pileup, coverage, indexing, and CRAM references.
Evaluate scientific claims and evidence quality. Use for assessing experimental design validity, identifying biases and confounders, applying evidence grading frameworks (GRADE, Cochrane Risk of Bias), or teaching critical analysis. Best for understanding evidence quality, identifying flaws. For formal peer review writing use peer-review.
Use when a user asks to debug or fix failing GitHub PR checks that run in GitHub Actions; use `gh` to inspect checks and logs, summarize failure context, draft a fix plan, and implement only after explicit approval. Treat external providers (for example Buildkite) as out of scope and report only the details URL.
> Create, build, deploy, and localize declarative agents for M365 Copilot and Teams. USE THIS SKILL for ANY task involving a declarative agent — including localization, scaffolding, editing manifests, adding capabilities, and deploying. Localization requires tokenized manifests and language files that only this skill knows how to produce. "scaffold an agent", "new agent project", "add a capability", "add a plugin", "configure my agent", "deploy my agent", "fix my agent manifest", "edit my agent", "localize my agent", "add localization", "translate my agent", "multi-language agent", "add an API plugin", "add an MCP plugin", "add OAuth to my plugin", "review instructions", "improve instructions", "fix my instructions"
Documentation generation workflow covering API docs, architecture docs, README files, code comments, and technical writing.
Take mapbox/mapbox-google-maps-migration from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.