Skip to main content

Migrating to Google Maps Advanced Markers: The 2025 Guide

 If you opened your browser console recently and saw google.maps.Marker is deprecated, you are not alone. Google has officially sunset the legacy Marker class in favor of google.maps.marker.AdvancedMarkerElement.

This isn't just a syntax swap; it is a fundamental architectural shift in how the Maps JavaScript API renders points of interest. While the legacy class was limited to static images and canvas overlays, the new API leverages the DOM and WebGL for better performance and customization.

This guide provides a rigorous, step-by-step migration path for replacing legacy markers with Advanced Markers using modern JavaScript (ES2024), focusing on the critical prerequisite: the Map ID.

The Root Cause: Why Deprecate google.maps.Marker?

To understand the fix, we must understand the engineering constraints of the legacy system.

  1. Raster vs. Vector: The legacy Marker class relied heavily on raster images or basic canvas drawing. It struggled to maintain performance when rendering hundreds of markers or when handling fluid animations (60fps).
  2. DOM Accessibility: Legacy markers were difficult to style with CSS. Developers resorted to complex "OverlayView" workarounds just to create a custom HTML tooltip or marker.
  3. WebGL Integration: AdvancedMarkerElement is built to work seamlessly with Vector Maps. This allows markers to interact with 3D building data, maintain collision detection, and handle occlusion (hiding markers behind buildings) natively.

The new class treats a marker not just as a pixel on a map, but as a fully interactive DOM element anchored to a geospatial coordinate.

Prerequisite: The Map ID (Critical Step)

You cannot use AdvancedMarkerElement without a Map ID. If you attempt to instantiate the new marker class on a map without a valid ID (or with only standard API keys), the markers will fail to render or revert to legacy behavior.

Creating a Map ID

Before writing code, execute these steps in the Google Cloud Console:

  1. Navigate to Google Maps Platform > Map Management.
  2. Click Create Map ID.
  3. Set the "Map type" to JavaScript.
  4. Select Vector as the map type (required for full feature parity like tilt/rotation).
  5. Copy the resulting Map ID string (e.g., 8e0a97af938621).

Implementation: The Modern Migration Pattern

We will migrate from the legacy script-tag loading method to the modern Dynamic Library Import strategy. This ensures tree-shaking compatibility and cleaner dependency management.

1. Initializing the Map with Vector Support

We use google.maps.importLibrary to load only the necessary chunks.

// index.js

// Define the init function
async function initMap() {
  // 1. Dynamic Import: Load the 'maps' library
  const { Map } = await google.maps.importLibrary("maps");
  
  // 2. Initialize the Map with the REQUIRED mapId
  // Replace 'YOUR_MAP_ID' with the ID generated in Cloud Console
  const map = new Map(document.getElementById("map"), {
    center: { lat: 47.6062, lng: -122.3321 }, // Seattle coordinates
    zoom: 12,
    mapId: "YOUR_MAP_ID", // Critical for Advanced Markers
    
    // Optional: optimize controls
    disableDefaultUI: false,
    clickableIcons: false, 
  });

  return map;
}

// Execute
const mapInstance = await initMap();

2. Creating an Advanced Marker

The legacy method required complex icon objects. The new method uses PinElement for standard customization or raw DOM nodes for complete control.

You must import the marker library specifically.

async function addMarkers(map) {
  // 1. Load the marker library
  const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker");

  // 2. Define the position
  const position = { lat: 47.6062, lng: -122.3321 };

  // 3. Customize the Pin (Optional)
  // PinElement replaces the legacy 'icon' property for simple styling
  const pin = new PinElement({
    background: "#FBBC04",
    borderColor: "#137333",
    glyphColor: "white",
    scale: 1.1,
  });

  // 4. Instantiate the AdvancedMarkerElement
  const marker = new AdvancedMarkerElement({
    map,
    position,
    title: "Seattle Center",
    content: pin.element, // Pass the DOM element from PinElement
    gmpClickable: true,   // Explicitly enable accessibility/clicks
  });
  
  return marker;
}

Deep Dive: Custom HTML Markers (The CSS Solution)

The most powerful feature of the migration is the ability to use raw HTML and CSS. You no longer need OverlayView.

The HTML/CSS Structure

/* style.css */
.custom-price-marker {
  background-color: white;
  border-radius: 8px;
  padding: 8px 12px;
  font-family: 'Inter', sans-serif;
  font-weight: 600;
  font-size: 14px;
  color: #1f2937;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  border: 1px solid #e5e7eb;
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  cursor: pointer;
  position: relative;
}

/* Add a pointer arrow using pseudo-elements */
.custom-price-marker::after {
  content: "";
  position: absolute;
  bottom: -6px;
  left: 50%;
  transform: translateX(-50%);
  border-width: 6px 6px 0;
  border-style: solid;
  border-color: #fff transparent transparent transparent;
}

/* Hover state handled entirely by CSS */
.custom-price-marker:hover {
  transform: scale(1.1) translateY(-2px);
  background-color: #4f46e5; /* Indigo-600 */
  color: white;
  border-color: #4f46e5;
}

.custom-price-marker:hover::after {
  border-top-color: #4f46e5;
}

The JavaScript Implementation

async function createCustomHTMLMarker(map, price, lat, lng) {
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");

  // 1. Create the DOM node natively
  const priceTag = document.createElement("div");
  priceTag.className = "custom-price-marker";
  priceTag.textContent = `$${price}`;

  // 2. Create the marker using the DOM node as content
  const marker = new AdvancedMarkerElement({
    map,
    position: { lat, lng },
    content: priceTag,
    title: `Price: $${price}`, // Accessibility label
  });

  // 3. Attach Event Listeners directly to the DOM node
  // This is cleaner than the maps API 'click' listener
  priceTag.addEventListener("click", (e) => {
    // Prevent map click propagation if necessary
    e.stopPropagation(); 
    console.log(`Clicked price: ${price}`);
    
    // Toggle active class for styling
    priceTag.classList.toggle("active");
  });
}

Handling Edge Cases and Pitfalls

1. Z-Index and Stacking Context

In the legacy API, zIndex was a property of the marker options. In AdvancedMarkerElement, styling is handled via the DOM. If you need to manage stacking order (e.g., a selected marker should be on top), utilize the zIndex style on the content element.

// Bring to front on click
priceTag.addEventListener("click", () => {
  priceTag.style.zIndex = "999";
});

2. Performance with Large Datasets

Advanced Markers are DOM nodes. While efficient, rendering 5,000 DOM nodes is heavier than rendering 5,000 canvas pixels.

  • Solution: Use @googlemaps/markerclusterer. The new library supports AdvancedMarkerElement.
  • Optimization: Ensure you remove markers from the map (marker.map = null) when they go out of the viewport if you are implementing custom bounding-box logic.

3. Missing dragend Events

The new markers support dragging, but the event structure has changed slightly. You now listen for the dragend event on the marker instance, but the data retrieval is strictly strictly property-based.

marker.gmpDraggable = true;
marker.addEventListener("gmp-click", () => {
    // Standard click
});
marker.addEventListener("dragend", (event) => {
    const newPos = marker.position;
    console.log("New LatLng:", newPos.lat, newPos.lng);
});

Conclusion

The migration to AdvancedMarkerElement requires initial infrastructure work—specifically setting up the Cloud Console Map ID—but the payoff is substantial. You gain direct DOM access for styling, native CSS transitions, and accessibility improvements that were previously impossible.

Start by creating your Map ID today, swapping your loader logic to dynamic imports, and converting your markers incrementally. The legacy Marker class is deprecated, and postponing the migration creates technical debt that will be harder to unwind as the V3 API continues to evolve.