
Mapbox Google Maps Migration
- 955 installs
- 71 repo stars
- Updated August 4, 2026
- mapbox/mapbox-agent-skills
mapbox-google-maps-migration is a Mapbox agent skill that converts existing Google Maps implementations into Mapbox GL JS with correct coordinate handling, layer patterns, and API swaps for developers migrating map UIs.
About
mapbox-google-maps-migration is an official Mapbox agent skill that walks developers through swapping Google Maps Platform APIs to Mapbox GL JS. The guide documents critical differences: Google uses {lat, lng} objects while Mapbox expects [lng, lat] arrays, Google favors imperative DOM markers while Mapbox uses declarative data-driven WebGL layers, and performance shifts from slowing around 500+ markers to handling 10,000+ points. Developers reach for this skill when refactoring map initialization, layers, sources, and event handlers without relearning Mapbox from generic docs. It is a migration quick reference with side-by-side API equivalents rather than a greenfield mapping tutorial.
- Provides critical differences between Google Maps (imperative, DOM, lat-lng objects) and Mapbox GL JS (declarative, WebG
- Includes an 8-step quick migration checklist covering package install, token setup, coordinate swapping, and clustering
- Shows side-by-side code examples for map initialization, marker handling, and geocoding
- Recommends performance best practices such as Symbol layers for 100+ markers and clustering for 500+ points
- Delivers ready-to-adapt JavaScript patterns that work with Claude Code, Cursor, and similar agents
Mapbox Google Maps Migration by the numbers
- 955 all-time installs (skills.sh)
- +33 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #403 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-google-maps-migrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 955 |
|---|---|
| repo stars | ★ 71 |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | mapbox/mapbox-agent-skills ↗ |
How do you migrate Google Maps to Mapbox GL JS?
Rapidly convert an existing Google Maps implementation into Mapbox GL JS with correct coordinate handling, layer patterns, and API swaps.
Who is it for?
Frontend developers replacing Google Maps Platform in an existing web or mobile map UI who need API equivalents and coordinate migration patterns.
Skip if: Greenfield mapping projects with no Google Maps codebase to convert or teams staying on Google Maps without any Mapbox adoption plan.
When should I use this skill?
The user is converting Google Maps to Mapbox GL JS, mentions lat/lng vs lng/lat coordinates, or needs WebGL layer patterns instead of DOM markers.
What you get
Mapbox GL JS map initialization, converted layers and sources, and corrected [lng, lat] coordinate handling replacing Google Maps APIs.
- Mapbox GL JS map module
- Migrated layer and source definitions
By the numbers
- Google Maps slows with 500+ markers; Mapbox GL JS handles 10,000+ points via WebGL
Files
Mapbox Google Maps Migration Skill
Comprehensive guidance for migrating from Google Maps Platform to Mapbox GL JS. Provides API equivalents, pattern translations, and strategies for successful migration.
Core Philosophy Differences
Google Maps: Imperative & Object-Oriented
- Create objects (Marker, Polygon, etc.)
- Add to map with
.setMap(map) - Update properties with setters
- Heavy reliance on object instances
Mapbox GL JS: Declarative & Data-Driven
- Add data sources
- Define layers (visual representation)
- Style with JSON
- Update data, not object properties
Key Insight: Mapbox treats everything as data + styling, not individual objects.
Map Initialization
Google Maps
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.7749, lng: -122.4194 },
zoom: 12,
mapTypeId: 'roadmap' // or 'satellite', 'hybrid', 'terrain'
});Mapbox GL JS
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:
- Coordinate order: Google uses
{lat, lng}, Mapbox uses[lng, lat] - Authentication: Google uses API key in script tag, Mapbox uses access token in code
- Styling: Google uses map types, Mapbox uses full style URLs
API Equivalents Reference
Map Methods
| 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 |
Map Events
| 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 |
Markers and Points
Simple Marker
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();Multiple Markers
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.
Info Windows / Popups
Google Maps
const infowindow = new google.maps.InfoWindow({
content: '<h3>Title</h3><p>Content</p>'
});
marker.addListener('click', () => {
infowindow.open(map, marker);
});Mapbox GL JS
// 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);
});Migration Strategy
Step 1: Audit Current Implementation
Identify all Google Maps features you use:
- [ ] Basic map with markers
- [ ] Info windows/popups
- [ ] Polygons/polylines
- [ ] Geocoding
- [ ] Directions
- [ ] Clustering
- [ ] Custom styling
- [ ] Drawing tools
- [ ] Street View (no Mapbox equivalent)
- [ ] Other advanced features
Step 2: Set Up Mapbox
<!-- 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" />Step 3: Convert Core Map
Start with basic map initialization:
1. Replace new google.maps.Map() with new mapboxgl.Map() 2. Fix coordinate order (lat,lng -> lng,lat) 3. Update zoom/center
Step 4: Convert Features One by One
Prioritize by complexity:
1. Easy: Map controls, basic markers 2. Medium: Popups, polygons, lines 3. Complex: Clustering, custom styling, data updates
Step 5: Update Event Handlers
Change event syntax:
google.maps.event.addListener()->map.on()- Update event property names (
latLng->lngLat)
Step 6: Optimize for Mapbox
Take advantage of Mapbox features:
- Convert multiple markers to data-driven layers
- Use clustering (built-in)
- Leverage vector tiles for custom styling
- Use expressions for dynamic styling
Step 7: Test Thoroughly
- Cross-browser testing
- Mobile responsiveness
- Performance with real data volumes
- Touch/gesture interactions
Gotchas and Common Issues
Coordinate Order
// Google Maps
{ lat: 37.7749, lng: -122.4194 }
// Mapbox (REVERSED!)
[-122.4194, 37.7749]Always double-check coordinate order!
Event Properties
// 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);
});Timing Issues
// Google Maps - immediate
const marker = new google.maps.Marker({ map: map });
// Mapbox - wait for load
map.on('load', () => {
map.addSource(...);
map.addLayer(...);
});Removing Features
// Google Maps
marker.setMap(null);
// Mapbox - must remove both
map.removeLayer('layer-id');
map.removeSource('source-id');Updating Data Without Flash
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({ ... });When NOT to Migrate
Consider staying with Google Maps if:
- Street View is critical - Mapbox doesn't have equivalent
- Tight Google Workspace integration - Places API deeply integrated
- Already heavily optimized - Migration cost > benefits
- Team expertise - Retraining costs too high
- Short-term project - Not worth migration effort
Quick Reference: Side-by-Side Comparison
// 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!
Additional Resources
- Mapbox GL JS Documentation
- Official Google Maps to Mapbox Migration Guide
- Mapbox Examples
- Style Specification
Integration with Other Skills
Works with:
- mapbox-web-integration-patterns: Framework-specific migration guidance
- mapbox-web-performance-patterns: Optimize after migration
- mapbox-token-security: Secure your Mapbox tokens properly
- mapbox-geospatial-operations: Use Mapbox's geospatial tools effectively
- mapbox-search-patterns: Migrate geocoding/search functionality
Reference Files
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, Geocoding
- `references/directions-controls.md` — Directions/Routing, Controls
- `references/clustering-styling.md` — Clustering, Styling/Appearance
- `references/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 Checklist
To load a reference, read the file relative to this skill directory, e.g.:
Load references/shapes-geocoding.mdGoogle Maps to Mapbox GL JS Migration Guide
Quick reference for migrating from Google Maps Platform to Mapbox GL JS with API equivalents and patterns.
Critical Differences
| Aspect | Google Maps | Mapbox GL JS |
|---|---|---|
| Coordinates | {lat, lng} objects | [lng, lat] arrays |
| Philosophy | Imperative (objects) | Declarative (data-driven) |
| Rendering | DOM elements | WebGL (much faster) |
| Performance | Slow with 500+ markers | Fast with 10,000+ points |
| Initialization | new google.maps.Map() | new mapboxgl.Map() |
Quick Migration Checklist
✅ Install mapbox-gl package ✅ Get Mapbox access token ✅ Swap coordinate order (lat,lng → lng,lat) ✅ Replace Google Maps API with Mapbox equivalents ✅ Use Symbol layers for 100+ markers (not HTML markers) ✅ Add clustering for 500+ points ✅ Update geocoding to Mapbox Geocoding API ✅ Test all functionality
API Equivalents
Map Initialization
// Google Maps
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.7749, lng: -122.4194 },
zoom: 12
});
// Mapbox GL JS
mapboxgl.accessToken = 'pk.your_token';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749], // Note: [lng, lat]
zoom: 12
});Individual Markers (< 50 points)
// Google Maps
const marker = new google.maps.Marker({
position: { lat: 37.7749, lng: -122.4194 },
map: map
});
// Mapbox (equivalent approach)
const marker = new mapboxgl.Marker().setLngLat([-122.4194, 37.7749]).addTo(map);Many Markers (100+ points) - Performance Critical
// ❌ Google Maps: DOM-based (slow with 500+ markers)
locations.forEach((loc) => {
new google.maps.Marker({
position: { lat: loc.lat, lng: loc.lng },
map: map
});
});
// ✅ Mapbox: WebGL-based (fast with 10,000+ points)
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: locations.map((loc) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [loc.lng, loc.lat] }
}))
}
});
map.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
layout: {
'icon-image': 'marker-15'
}
});Performance Note: Google Maps renders ALL markers as DOM elements (even with Data Layer). Mapbox uses WebGL for Symbol/Circle layers = 10-100x faster for large datasets.
Clustering (500+ points)
// Google Maps (requires MarkerClusterer library)
import MarkerClusterer from '@googlemaps/markerclustererplus';
const clusterer = new MarkerClusterer(map, markers);
// Mapbox (built-in)
map.addSource('points', {
type: 'geojson',
data: geojson,
cluster: true,
clusterRadius: 50
});Info Windows / Popups
// Google Maps
const infowindow = new google.maps.InfoWindow({
content: '<h3>Title</h3>'
});
infowindow.open(map, marker);
// Mapbox
const popup = new mapboxgl.Popup().setHTML('<h3>Title</h3>').setLngLat([-122.4194, 37.7749]).addTo(map);
// Or attach to marker
marker.setPopup(popup);Events
// Google Maps
marker.addListener('click', () => {
/* ... */
});
map.addListener('click', (e) => {
const lat = e.latLng.lat();
const lng = e.latLng.lng();
});
// Mapbox
marker.on('click', () => {
/* ... */
});
map.on('click', (e) => {
const [lng, lat] = [e.lngLat.lng, e.lngLat.lat];
});Geocoding
// Google Maps
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: '1600 Amphitheatre Parkway' }, (results) => {
map.setCenter(results[0].geometry.location);
});
// Mapbox
fetch(
`https://api.mapbox.com/search/geocode/v6/forward?q=1600+Amphitheatre+Parkway&access_token=${mapboxgl.accessToken}`
)
.then((r) => r.json())
.then((data) => {
const [lng, lat] = data.features[0].geometry.coordinates;
map.setCenter([lng, lat]);
});Directions
// Google Maps
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: 'San Francisco',
destination: 'Los Angeles',
travelMode: 'DRIVING'
},
(result) => {
/* ... */
}
);
// Mapbox
fetch(
`https://api.mapbox.com/directions/v5/mapbox/driving/-122.4194,37.7749;-118.2437,34.0522?access_token=${mapboxgl.accessToken}`
)
.then((r) => r.json())
.then((data) => {
const route = data.routes[0].geometry;
// Display route on map
});Polygons/Shapes
// Google Maps
const polygon = new google.maps.Polygon({
paths: coordinates,
map: map
});
// Mapbox
map.addSource('polygon', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinates] // Note: Array of arrays
}
}
});
map.addLayer({
id: 'polygon',
type: 'fill',
source: 'polygon',
paint: {
'fill-color': '#088',
'fill-opacity': 0.5
}
});Coordinate Order - CRITICAL
Most common migration bug:
// ❌ Google Maps order (lat, lng)
{ lat: 37.7749, lng: -122.4194 }
// ✅ Mapbox order (lng, lat)
[-122.4194, 37.7749]
// Remember: Mapbox follows GeoJSON standard (longitude first)Performance Advantages
Mapbox is significantly faster for:
1. Large datasets: 500+ markers (Symbol layers vs DOM markers) 2. Data visualization: Choropleth, heatmaps (WebGL rendering) 3. Custom styling: Full control over every visual element 4. Vector tiles: Efficient data loading and rendering
When Mapbox wins:
- Rendering 10,000+ points smoothly
- Custom map styles (not just pins on a map)
- Data-driven visualizations
- Performance-critical applications
When Google Maps might be better:
- Need Street View
- Heavy Google Workspace integration
- Places API is critical
- Team has deep Google Maps expertise
Styling Comparison
// Google Maps (limited styling)
const styledMapType = new google.maps.StyledMapType([{ elementType: 'geometry', stylers: [{ color: '#242f3e' }] }]);
// Mapbox (full control)
map.setStyle('mapbox://styles/mapbox/dark-v11');
// Or create custom styles in Mapbox StudioMapbox Styles:
streets-v12- Standard streetsoutdoors-v12- Hiking/outdoorlight-v11/dark-v11- Minimalsatellite-v9/satellite-streets-v12- Imagery- Custom styles via Mapbox Studio
Common Migration Patterns
Store Locator
Google Maps: Create marker for each store, add click listeners, show info windows Mapbox: Use Symbol layer + click events + popups (much faster for 100+ stores)
Route Display
Google Maps: DirectionsRenderer Mapbox: Fetch route from Directions API, add as Line layer
Heatmaps
Google Maps: HeatmapLayer (DOM-based) Mapbox: Heatmap layer (WebGL-based, much faster)
Token & Pricing
Google Maps:
- Requires API key
- Pay per map load + API calls
- Free tier: $200/month credit
Mapbox:
- Requires access token (pk.\* for client-side)
- Pay per map load + API calls
- Free tier: 50,000 map loads/month
Token setup:
// Store in environment variables
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN;Testing Migration
Checklist:
1. ✅ Map displays at correct location 2. ✅ All markers/pins visible 3. ✅ Click events work 4. ✅ Popups display correctly 5. ✅ Geocoding returns results 6. ✅ Directions routing works 7. ✅ Performance improved (if using Symbol layers) 8. ✅ Mobile works (touch events)
Migration Strategy
Phase 1: Setup
- Install Mapbox GL JS
- Get access token
- Create test page
Phase 2: Core Migration
- Initialize map
- Swap coordinate order
- Convert markers (use Symbol layers for 100+)
- Migrate popups/info windows
Phase 3: Features
- Geocoding
- Directions
- Custom styling
- Events
Phase 4: Optimization
- Add clustering (if 500+ points)
- Implement proper cleanup
- Test performance
- Mobile optimization
Quick Wins
Easy migrations (mostly drop-in replacements):
- Basic map initialization
- Individual markers (< 50)
- Popups
- Map controls
- Click events
Requires rethinking (but worth it):
- Large marker sets → Symbol layers (10-100x faster)
- Custom styling → Mapbox Studio
- Heatmaps → Heatmap layers (WebGL)
When NOT to Migrate
Consider staying with Google Maps if:
- Street View is critical
- Heavy Places API usage
- Team has deep Google Maps expertise
- Already heavily optimized
- Short-term project
{
"skill_name": "mapbox-google-maps-migration",
"evals": [
{
"id": 1,
"prompt": "I'm migrating a store locator from Google Maps to Mapbox. In Google Maps I created `new google.maps.Marker()` for each of my 500 store locations. I've translated this directly and I'm now creating `new mapboxgl.Marker()` for each store. My map is noticeably sluggish when displaying all 500 markers. What's the issue and how do I fix it?",
"expectations": [
"Identifies that mapboxgl.Marker() creates individual DOM elements — 500 DOM markers causes performance issues (not GPU-rendered)",
"Recommends switching to a GeoJSON source + symbol layer for large marker counts (100+ markers)",
"Explains that symbol layers are WebGL-rendered and can handle thousands of points efficiently",
"Shows the correct pattern: map.addSource() with GeoJSON FeatureCollection, then map.addLayer() with type 'symbol' or 'circle'"
]
},
{
"id": 2,
"prompt": "I want to remove a GeoJSON layer from my Mapbox map. I'm calling `map.removeSource('stores')` but getting this error: 'Source \"stores\" cannot be removed while layer \"stores-layer\" is using it.' In Google Maps I just called `polygon.setMap(null)`. Why is Mapbox different and how do I fix it?",
"expectations": [
"Explains that Mapbox separates data (sources) from visual representation (layers) — multiple layers can reference one source",
"To remove cleanly: call map.removeLayer('stores-layer') first, then map.removeSource('stores')",
"Explains why this design exists — the same source can power multiple layers (e.g., both a circle layer and a label layer)",
"Contrasts with Google Maps' object model where the visual and data are combined in one object"
]
},
{
"id": 3,
"prompt": "My Mapbox map loads a GeoJSON source with all 1000 stores. When a user filters by category, I call `map.removeLayer('stores')`, `map.removeSource('stores')`, then `map.addSource()` and `map.addLayer()` with filtered data. It works but there's a visible flash when the layer disappears and reappears. Is there a better pattern?",
"expectations": [
"Recommends map.getSource('stores').setData(filteredGeoJSON) to update data in place — no layer removal, no flash",
"OR recommends map.setFilter('stores', filterExpression) with a Mapbox GL JS filter expression — even better, no data re-upload, the full dataset stays in GPU memory",
"Explains that remove+re-add reinitializes WebGL resources causing the flash — setData() or setFilter() avoids this",
"Shows a concrete example of setFilter() with an expression like ['==', ['get', 'category'], selectedCategory]"
]
}
]
}
API Services, Pricing, Plugins, Framework Integration, and Testing
API Services Comparison
| Service | Google Maps | Mapbox | Notes |
|---|---|---|---|
| Geocoding | Geocoding API | Geocoding API | Similar capabilities |
| Reverse Geocoding | ✅ | ✅ | Similar |
| Directions | Directions API | Directions API | Mapbox has traffic-aware routing |
| Distance Matrix | Distance Matrix API | Matrix API | Similar |
| Isochrones | ❌ | ✅ | Mapbox exclusive |
| Optimization | ❌ | ✅ | Mapbox exclusive (TSP) |
| Street View | ✅ | ❌ | Google exclusive |
| Static Maps | ✅ | ✅ | Both supported |
| Satellite Imagery | ✅ | ✅ | Both supported |
| Tilesets | Limited | Full API | Mapbox more flexible |
Pricing Differences
Google Maps Platform
- Charges per API call
- Free tier: $200/month credit
- Different rates for different APIs
- Can get expensive with high traffic
Mapbox
- Charges per map load
- Free tier: 50,000 map loads/month
- Unlimited API requests per map session
- More predictable costs
Migration Tip: Understand how pricing models differ for your use case.
Plugins and Extensions
Google Maps Plugins -> Mapbox Alternatives
| Google Maps Plugin | Mapbox Alternative |
|---|---|
| MarkerClusterer | Built-in clustering |
| Drawing Manager | @mapbox/mapbox-gl-draw |
| Geocoder | @mapbox/mapbox-gl-geocoder |
| Directions | @mapbox/mapbox-gl-directions |
| - | @mapbox/mapbox-gl-traffic |
| - | @mapbox/mapbox-gl-compare |
Framework Integration
React
Google Maps:
import { GoogleMap, Marker } from '@react-google-maps/api';Mapbox:
import Map, { Marker } from 'react-map-gl';
// or
import { useMap } from '@mapbox/mapbox-gl-react';Vue
Google Maps:
import { GoogleMap } from 'vue3-google-map';Mapbox:
import { MglMap } from 'vue-mapbox';See mapbox-web-integration-patterns skill for detailed framework guidance.
Testing Strategy
Unit Tests
// Mock mapboxgl
jest.mock('mapbox-gl', () => ({
Map: jest.fn(() => ({
on: jest.fn(),
addSource: jest.fn(),
addLayer: jest.fn()
})),
Marker: jest.fn()
}));Integration Tests
- Test map initialization
- Test data loading and updates
- Test user interactions (click, pan, zoom)
- Test API integrations (geocoding, directions)
Visual Regression Tests
- Compare before/after screenshots
- Ensure visual parity with Google Maps version
Checklist: Migration Complete
- [ ] Map initializes correctly
- [ ] All markers/features display
- [ ] Click/hover interactions work
- [ ] Popups/info windows display
- [ ] Geocoding integrated
- [ ] Directions/routing working
- [ ] Custom styling applied
- [ ] Controls positioned correctly
- [ ] Mobile/touch gestures work
- [ ] Performance is acceptable
- [ ] Cross-browser tested
- [ ] API keys secured
- [ ] Error handling in place
- [ ] Analytics/monitoring updated
- [ ] Documentation updated
- [ ] Team trained on Mapbox
Clustering and Styling
Clustering
Google Maps
// Requires MarkerClusterer library
import MarkerClusterer from '@googlemaps/markerclustererplus';
const markers = locations.map((loc) => new google.maps.Marker({ position: loc, map: map }));
new MarkerClusterer(map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'
});Mapbox GL JS
// Built-in clustering support
map.addSource('points', {
type: 'geojson',
data: geojsonData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
// Cluster circles
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points',
filter: ['has', 'point_count'],
paint: {
'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
}
});
// Cluster count labels
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'points',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-size': 12
}
});
// Unclustered points
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'points',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 8
}
});Key Advantage: Mapbox clustering is built-in and highly performant.
Styling and Appearance
Map Types vs. Styles
Google Maps:
- Limited map types: roadmap, satellite, hybrid, terrain
- Styling via
stylesarray (complex)
Mapbox GL JS:
- Full control over every visual element
- Pre-built styles: standard, standard-satellite, streets, outdoors, light, dark
- Custom styles via Mapbox Studio for unique branding and design
- Dynamic styling based on data properties
- For classic styles (pre Mapbox Standard) you can modify style programmatically by using the setPaintProperty()
Custom Styling Example
Google Maps:
const styledMapType = new google.maps.StyledMapType(
[
{ elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
{ elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] }
// ... many more rules
],
{ name: 'Dark' }
);
map.mapTypes.set('dark', styledMapType);
map.setMapTypeId('dark');Mapbox GL JS:
// Use pre-built style
map.setStyle('mapbox://styles/mapbox/dark-v11');
// Or create custom style in Mapbox Studio and reference it
map.setStyle('mapbox://styles/yourusername/your-style-id');
// Modify classic styles programmatically
map.setPaintProperty('water', 'fill-color', '#242f3e');Data Updates, Performance, and Common Migration Patterns
Data Updates
Google Maps
// Update marker position
marker.setPosition({ lat: 37.7849, lng: -122.4094 });
// Update polygon path
polygon.setPath(newCoordinates);Mapbox GL JS
// Update source data
map.getSource('points').setData(newGeojsonData);
// Or update specific features
const source = map.getSource('points');
const data = source._data;
data.features[0].geometry.coordinates = [-122.4094, 37.7849];
source.setData(data);Performance Considerations
Google Maps
- Individual objects for each feature
- Can be slow with 1000+ markers
- Requires MarkerClusterer for performance
Mapbox GL JS
- Data-driven rendering
- WebGL-based (hardware accelerated)
- Handles 10,000+ points smoothly
- Built-in clustering
Migration Tip: If you have performance issues with Google Maps (many markers), Mapbox will likely perform significantly better.
Common Migration Patterns
Pattern 1: Store Locator
Google Maps approach:
1. Create marker for each store 2. Add click listeners to each marker 3. Show info window on click
Mapbox approach:
1. Add all stores as GeoJSON source 2. Add symbol layer for markers 3. Use layer click event for all markers 4. More performant, cleaner code
Pattern 2: Drawing Tools
Google Maps:
- Use Drawing Manager library
- Creates overlay objects
Mapbox:
- Use Mapbox Draw plugin
- More powerful, customizable
- Better for complex editing
Pattern 3: Heatmaps
Google Maps:
const heatmap = new google.maps.visualization.HeatmapLayer({
data: points,
map: map
});Mapbox:
map.addLayer({
id: 'heatmap',
type: 'heatmap',
source: 'points',
paint: {
'heatmap-intensity': 1,
'heatmap-radius': 50,
'heatmap-color': ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(0,0,255,0)', 0.5, 'lime', 1, 'red']
}
});Directions, Routing, and Controls
Directions / Routing
Google Maps
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);
directionsService.route(
{
origin: 'San Francisco, CA',
destination: 'Los Angeles, CA',
travelMode: 'DRIVING'
},
(response, status) => {
if (status === 'OK') {
directionsRenderer.setDirections(response);
}
}
);Mapbox GL JS
// Use Mapbox Directions API
const origin = [-122.4194, 37.7749];
const destination = [-118.2437, 34.0522];
fetch(
`https://api.mapbox.com/directions/v5/mapbox/driving/${origin.join(',')};${destination.join(',')}?geometries=geojson&access_token=${mapboxgl.accessToken}`
)
.then((response) => response.json())
.then((data) => {
const route = data.routes[0].geometry;
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: route
}
});
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
paint: {
'line-color': '#3887be',
'line-width': 5
}
});
});
// Or use @mapbox/mapbox-gl-directions plugin
const directions = new MapboxDirections({
accessToken: mapboxgl.accessToken
});
map.addControl(directions, 'top-left');Controls
Google Maps
// Controls are automatic, can configure:
map.setOptions({
zoomControl: true,
mapTypeControl: true,
streetViewControl: false,
fullscreenControl: true
});Mapbox GL JS
// Add controls explicitly
map.addControl(new mapboxgl.NavigationControl()); // Zoom + rotation
map.addControl(new mapboxgl.FullscreenControl());
map.addControl(new mapboxgl.GeolocateControl());
map.addControl(new mapboxgl.ScaleControl());
// Position controls
map.addControl(new mapboxgl.NavigationControl(), 'top-right');Shapes, Custom Icons, and Geocoding
Polygons and Shapes
Google Maps
const polygon = new google.maps.Polygon({
paths: [
{ lat: 37.7749, lng: -122.4194 },
{ lat: 37.7849, lng: -122.4094 },
{ lat: 37.7649, lng: -122.4094 }
],
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: map
});Mapbox GL JS
map.addSource('polygon', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[
[-122.4194, 37.7749],
[-122.4094, 37.7849],
[-122.4094, 37.7649],
[-122.4194, 37.7749] // Close the ring
]
]
}
}
});
map.addLayer({
id: 'polygon-layer',
type: 'fill',
source: 'polygon',
paint: {
'fill-color': '#FF0000',
'fill-opacity': 0.35
}
});
// Add outline
map.addLayer({
id: 'polygon-outline',
type: 'line',
source: 'polygon',
paint: {
'line-color': '#FF0000',
'line-width': 2,
'line-opacity': 0.8
}
});Polylines / Lines
Google Maps
const line = new google.maps.Polyline({
path: [
{ lat: 37.7749, lng: -122.4194 },
{ lat: 37.7849, lng: -122.4094 }
],
strokeColor: '#0000FF',
strokeWeight: 3,
map: map
});Mapbox GL JS
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[-122.4194, 37.7749],
[-122.4094, 37.7849]
]
}
}
});
map.addLayer({
id: 'route-layer',
type: 'line',
source: 'route',
paint: {
'line-color': '#0000FF',
'line-width': 3
}
});Custom Icons and Symbols
Google Maps
const marker = new google.maps.Marker({
position: { lat: 37.7749, lng: -122.4194 },
map: map,
icon: {
url: 'marker.png',
scaledSize: new google.maps.Size(32, 32)
}
});Mapbox GL JS
Option 1: HTML Marker
const el = document.createElement('div');
el.className = 'marker';
el.style.backgroundImage = 'url(marker.png)';
el.style.width = '32px';
el.style.height = '32px';
new mapboxgl.Marker(el).setLngLat([-122.4194, 37.7749]).addTo(map);Option 2: Symbol Layer (Better Performance)
// Load image
map.loadImage('marker.png', (error, image) => {
if (error) throw error;
map.addImage('custom-marker', image);
map.addLayer({
id: 'markers',
type: 'symbol',
source: 'points',
layout: {
'icon-image': 'custom-marker',
'icon-size': 1
}
});
});Geocoding
Google Maps
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: '1600 Amphitheatre Parkway' }, (results, status) => {
if (status === 'OK') {
map.setCenter(results[0].geometry.location);
}
});Mapbox GL JS
// Use Mapbox Geocoding API v6
fetch(
`https://api.mapbox.com/search/geocode/v6/forward?q=1600+Amphitheatre+Parkway&access_token=${mapboxgl.accessToken}`
)
.then((response) => response.json())
.then((data) => {
const [lng, lat] = data.features[0].geometry.coordinates;
map.setCenter([lng, lat]);
});
// Or use mapbox-gl-geocoder plugin
const geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
mapboxgl: mapboxgl
});
map.addControl(geocoder);Related skills
How it compares
Use mapbox-google-maps-migration for API swap refactors from Google Maps; use Mapbox docs for brand-new maps with no legacy Google code.
FAQ
How do coordinates differ between Google Maps and Mapbox GL JS?
mapbox-google-maps-migration notes Google Maps uses {lat, lng} objects while Mapbox GL JS expects [lng, lat] arrays, and getting this order wrong is a common migration bug the skill addresses directly.
What performance difference does Mapbox GL JS offer over Google Maps markers?
mapbox-google-maps-migration documents Google Maps slowing with 500+ markers while Mapbox GL JS WebGL rendering stays fast with 10,000+ points using declarative data-driven layers.
Is Mapbox Google Maps Migration safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.