
📍 Cómo añadir múltiples marcadores personalizados con leyenda a Google Maps
Los marcadores son la forma principal de indicar un punto en un mapa. El clásico «pin» rojo de Google Maps es reconocible, pero cuando tiene cinco puntos de tipos diferentes, los usuarios se pierden: ¿cuál es una dirección exacta y cuál es un punto de referencia aproximado?
Esto ocurre constantemente: un catálogo de tiendas dividido en ubicaciones «propias» y «asociadas», un mapa de reparto con zonas, un panel logístico con tipos de carga. En cualquier lugar donde tenga más de tres puntos con significados distintos, se produce el caos sin iconos personalizados y una leyenda.
La solución son iconos de marcador personalizados y una leyenda que explique cada símbolo. En 20 minutos construirá un mapa con varios tipos de marcadores, y los usuarios entenderán qué es cada cosa de un vistazo. En la práctica, hemos visto una y otra vez que un par de pines diferentes y un bloque de leyenda en la esquina ahorran a los usuarios minutos de descifrado y evitan a los equipos de soporte decenas de preguntas del tipo «¿qué significa el círculo azul?».
A continuación, un desglose paso a paso usando JavaScript simple, sin frameworks. Usaremos coordenadas alrededor de la Torre Eiffel, un punto de referencia conveniente con una geometría reconocible. El código es intencionadamente mínimo, con cada método explicado línea por línea para que pueda adaptarlo a su tarea sin leer toda la documentación.
El material progresa de lo simple a lo complejo: primero obtendrá una clave de API y mostrará un mapa básico, luego añadirá marcadores con diferentes iconos, construirá una leyenda y, finalmente, tendrá un archivo HTML funcional que se ejecuta directamente. Todos los ejemplos se han probado en Google Chrome 120+, Firefox 121+ y Safari 17+.
💡 Resumen rápido:
- Obtenga una clave de la API de Google Maps e incluya la librería
- Prepare un array de puntos en JSON: nombre, coordenadas, tipo (exacto/aproximado)
- Asigne a cada tipo su propio icono mediante la propiedad
icon - Construya la leyenda como un elemento HTML y adjúntela al mapa mediante
map.controls - Resultado: un mapa, dos tipos visuales de marcador, leyenda en la esquina superior derecha
Obtención de una clave de la API de Google Maps
Trabajar con la API de JavaScript Maps requiere una clave. Si no tiene una, créela en Google Cloud Console.
Pasos rápidos: vaya a Console, cree un proyecto (o seleccione uno existente), habilite Maps JavaScript API, genere una clave. Para desarrollo local, basta con restringir la clave por referente HTTP; para producción, añada su dominio a la lista de permitidos. En proyectos reales siempre creamos una clave separada para cada aplicación: es más fácil hacer un seguimiento de las cuotas en Cloud Console y revocar el acceso de forma precisa si una clave se filtra.
Incluir la librería requiere una línea en <script>:
1 <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=drawMap"></script>
El atributo callback especifica qué función se ejecuta después de que la API se cargue. La escribiremos a continuación.

Importante: desde febrero de 2024, la clase
google.maps.Markerestá oficialmente marcada como obsoleta. Ha sido reemplazada porgoogle.maps.marker.AdvancedMarkerElement, que ofrece mejor rendimiento y admite HTML personalizado. El código siguiente utiliza elMarkerclásico (todavía funciona y es más fácil para empezar), y la sección de preguntas frecuentes incluye un enlace a la guía de migración para cambiar a AdvancedMarkerElement.
Inicialización del mapa y preparación de los datos
Primero, un contenedor para el mapa y un array de coordenadas. Use las coordenadas que desee; aquí tenemos cinco puntos alrededor de la Torre Eiffel en París, tres exactos y dos aproximados.
1 <div id="mapCanvas"></div>
Empaquetamos los datos en un array de objetos. El campo is_exact separará los marcadores en dos tipos: los que señalan una dirección exacta y los que indican un área general.
1 const locations = [ 2 { name: "Eiffel Tower", lat: 48.85837, lng: 2.294481, is_exact: true }, 3 { name: "UNESCO HQ", lat: 48.84956, lng: 2.306326, is_exact: false }, 4 { name: "Trocadéro", lat: 48.86278, lng: 2.28766, is_exact: false }, 5 { name: "Champ de Mars",lat: 48.85609, lng: 2.29820, is_exact: true }, 6 { name: "Bir-Hakeim", lat: 48.85032, lng: 2.28926, is_exact: false } 7 ];
Aquí JSON es simplemente un formato conveniente: legible, extensible, y cualquier backend puede devolver un array así en una línea.
El mapa en sí se inicializa con un objeto google.maps.Map. Céntrelo en el primer punto del array, establezca el zoom para que quepan los cinco:
1 const centerMap = new google.maps.LatLng(48.856, 2.294); 2 3 const map = new google.maps.Map(document.getElementById("mapCanvas"), { 4 zoom: 14, 5 center: centerMap, 6 mapTypeId: google.maps.MapTypeId.ROADMAP, 7 mapTypeControl: true, 8 fullscreenControl: false 9 });
Cómo añadir marcadores personalizados con distintos iconos
Sin un icono personalizado, cada llamada a google.maps.Marker dibuja el pin rojo estándar. Para distinguir tipos de puntos, proporcione su propia imagen en la propiedad icon.
Prepare dos iconos, por ejemplo marker_exact.png (un pin verde para direcciones exactas) y marker_approx.png (un círculo azul para ubicaciones aproximadas). Coloque los archivos en una carpeta images/ junto al HTML.
La lógica de selección del icono utiliza un operador ternario basado en el indicador is_exact:
1 function plotMarker(location) { 2 const iconFile = location.is_exact ? "marker_exact.png" : "marker_approx.png"; 3 4 new google.maps.Marker({ 5 position: new google.maps.LatLng(location.lat, location.lng), 6 icon: "images/" + iconFile, 7 title: location.name, 8 map: map 9 }); 10 }
Para colocar todos los marcadores a la vez, itere sobre el array:
1 function setMarkers(locations) { 2 for (let i = 0; i < locations.length; i++) { 3 plotMarker(locations[i]); 4 } 5 }
Listo. Cinco puntos, dos iconos, el mapa conoce cada marcador. En nuestra experiencia, en esta etapa conviene abrir la consola y verificar que los cinco marcadores se hayan renderizado: si no se encontró un icono en la ruta especificada, el marcador vuelve silenciosamente al pin predeterminado, y es posible que ni siquiera lo note visualmente. Pero el espectador aún no lo entiende. Necesita una leyenda.
Unas palabras sobre los iconos. El tamaño óptimo es 40×40 px para pantallas estándar y 80×80 px para Retina (especifique el archivo más grande en el atributo icon y establezca scaledSize como new google.maps.Size(40,40); la imagen se verá nítida en pantallas retina). El formato puede ser PNG, SVG o WebP, pero para pines personalizados preferimos SVG: pesa menos, escala sin pixelarse y es fácil de editar. Si aun así usa PNG, elija 24 bits con canal alfa; de lo contrario, aparece un fondo blanco alrededor del pin, lo cual desentona en mapas oscuros.
Un error común: especificar la ruta del archivo del icono relativa al HTML en lugar de relativa a la raíz del servidor. Coloque marker_exact.png en la misma carpeta que index.html y especifique "marker_exact.png" sin barra inicial. Si los iconos están en una subcarpeta como images/, la ruta será "images/marker_exact.png", exactamente como se muestra en el código anterior.
Cómo crear una leyenda para los marcadores
Una leyenda es simplemente un elemento del DOM común que colocamos en la zona de controles del mapa. Google Maps permite posicionar un control en una de las ubicaciones estándar: por ejemplo TOP_LEFT, TOP_CENTER o RIGHT_TOP. Para una leyenda, la esquina superior derecha tiene sentido.
Primero, describa el contenedor en HTML y dele estilo:
1 <div id="mapLegend"> 2 <h2>Обозначения</h2> 3 </div> 4 5 css 6 #mapLegend { 7 background: #fdfdfd; 8 color: #3c4750; 9 padding: 0 10px; 10 margin: 10px; 11 font-weight: bold; 12 opacity: 0.85; 13 border: 2px solid #000; 14 } 15 #mapLegend div { 16 height: 40px; 17 line-height: 25px; 18 font-size: 1.2em; 19 } 20 #mapLegend div img { 21 float: left; 22 margin-right: 10px; 23 } 24 #mapLegend h2 { 25 text-align: center; 26 }
Ahora rellene la leyenda con filas de «icono + etiqueta» y entréguela al mapa:
1 const legend = document.getElementById("mapLegend"); 2 3 const exactDiv = document.createElement("div"); 4 exactDiv.innerHTML = '<img src="images/marker_exact.png"> Точное местоположение'; 5 legend.appendChild(exactDiv); 6 7 const approxDiv = document.createElement("div"); 8 approxDiv.innerHTML = '<img src="images/marker_approx.png"> Примерное местоположение'; 9 legend.appendChild(approxDiv); 10 11 map.controls[google.maps.ControlPosition.RIGHT_TOP].push(legend);
El orden importa: primero construya legend como un nodo del DOM, luego insértelo. Si invierte este orden, el control permanece vacío.
Código completo: uniendo todas las piezas
Combine HTML, CSS y JavaScript en un solo archivo. Guárdelo como index.html, reemplace YOUR_API_KEY por una clave válida y ábralo en un navegador.

Aquí está el listado final:
1 <!doctype html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Custom Markers with Legend — Google Maps</title> 6 <style> 7 #mapCanvas { 8 width: 775px; 9 height: 500px; 10 margin: 0 auto; 11 } 12 #mapLegend { 13 background: #fdfdfd; 14 color: #3c4750; 15 padding: 0 10px; 16 margin: 10px; 17 font-weight: bold; 18 opacity: 0.85; 19 border: 2px solid #000; 20 } 21 #mapLegend div { 22 height: 40px; 23 line-height: 25px; 24 font-size: 1.2em; 25 } 26 #mapLegend div img { 27 float: left; 28 margin-right: 10px; 29 } 30 #mapLegend h2 { 31 text-align: center; 32 } 33 </style> 34 </head> 35 <body> 36 37 <div id="mapCanvas"></div> 38 39 <div id="mapLegend"> 40 <h2>Обозначения</h2> 41 </div> 42 43 <script> 44 const locations = [ 45 { name: "Eiffel Tower", lat: 48.85837, lng: 2.294481, is_exact: true }, 46 { name: "UNESCO HQ", lat: 48.84956, lng: 2.306326, is_exact: false }, 47 { name: "Trocadéro", lat: 48.86278, lng: 2.28766, is_exact: false }, 48 { name: "Champ de Mars",lat: 48.85609, lng: 2.29820, is_exact: true }, 49 { name: "Bir-Hakeim", lat: 48.85032, lng: 2.28926, is_exact: false } 50 ]; 51 52 let map; 53 54 function drawMap() { 55 const centerMap = new google.maps.LatLng(48.856, 2.294); 56 57 map = new google.maps.Map(document.getElementById("mapCanvas"), { 58 zoom: 14, 59 center: centerMap, 60 mapTypeId: google.maps.MapTypeId.ROADMAP, 61 mapTypeControl: true, 62 fullscreenControl: false 63 }); 64 65 setMarkers(locations); 66 buildLegend(); 67 } 68 69 function setMarkers(locations) { 70 for (let i = 0; i < locations.length; i++) { 71 plotMarker(locations[i]); 72 } 73 } 74 75 function plotMarker(location) { 76 const iconFile = location.is_exact ? "marker_exact.png" : "marker_approx.png"; 77 78 new google.maps.Marker({ 79 position: new google.maps.LatLng(location.lat, location.lng), 80 icon: "images/" + iconFile, 81 title: location.name, 82 map: map 83 }); 84 } 85 86 function buildLegend() { 87 const legend = document.getElementById("mapLegend"); 88 89 const exactDiv = document.createElement("div"); 90 exactDiv.innerHTML = '<img src="images/marker_exact.png"> Точное местоположение'; 91 legend.appendChild(exactDiv); 92 93 const approxDiv = document.createElement("div"); 94 approxDiv.innerHTML = '<img src="images/marker_approx.png"> Примерное местоположение'; 95 legend.appendChild(approxDiv); 96 97 map.controls[google.maps.ControlPosition.RIGHT_TOP].push(legend); 98 } 99 </script> 100 101 <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=drawMap"></script> 102 </body> 103 </html>
Reemplace YOUR_API_KEY y el mapa cobra vida. Añadir un tercer tipo de marcador implica otro icono, otro div en la leyenda y otra condición en plotMarker. Escala de forma lineal.
Al transferir este código a su proyecto, preste atención a dos cosas. Primero: en una aplicación real, los datos de locations probablemente provendrán de un backend mediante fetch o XMLHttpRequest, en lugar de estar incrustados en <script>. Segundo: si tiene más de diez puntos, mueva el array a un archivo .json separado y cárguelo de forma asíncrona; esto mantiene el HTML legible y evita que el mapa bloquee la renderización de la página.
Para producción con docenas de marcadores, añada agrupación mediante la librería @googlemaps/markerclusterer: agrupa los puntos cercanos en círculos con números y luego los expande en pines individuales al hacer zoom. Instálela vía npm (npm i @googlemaps/markerclusterer) y conéctela con tres líneas: new MarkerClusterer({ map, markers }). Sin agrupación, cincuenta marcadores al zoom de ciudad convierten el mapa en un revoltijo ilegible de iconos superpuestos.
⁉️🤔 Preguntas frecuentes
¿Puedo usar SVG en lugar de PNG para los iconos de los marcadores?
Sí, Google Maps acepta cualquier formato que el navegador pueda renderizar: PNG, SVG, WebP. El SVG es especialmente conveniente porque no se pixela al hacer zoom y pesa menos. Pase la ruta al archivo
.svgen la propiedadiconexactamente igual que haría con un PNG. Defina el tamaño del icono mediante los atributoswidth/heightdel propio archivo SVG: Google Maps los usará como base y los escalará según las densidades de pantalla.
¿Cómo migro de google.maps.Marker a AdvancedMarkerElement?
Desde febrero de 2024,
google.maps.Markerestá marcado como obsoleto. La nueva clasegoogle.maps.marker.AdvancedMarkerElementusa contenido HTML en lugar de la propiedadicon, por lo que puede insertar un botón, una insignia o un SVG animado directamente en el marcador. Incluya la librería de marcadores (&libraries=markeren la URL de la API) y, en el constructor, sustituyaiconporcontentcon un elemento del DOM:
1 > const marker = new google.maps.marker.AdvancedMarkerElement({ 2 > map, 3 > position: new google.maps.LatLng(location.lat, location.lng), 4 > title: location.name, 5 > content: document.createElement("img") 6 > }); 7 >
El contenido puede ser cualquier elemento del DOM: <img>, un <div> con fondo o SVG. La guía de migración oficial cubre cada escenario de sustitución paso a paso con ejemplos.
¿Cómo añado una leyenda en la versión móvil del mapa?
El control
RIGHT_TOPse superpone al mapa en pantallas estrechas (menos de 480 px). La solución es una media query de CSS: paramax-width: 480px, mueva la leyenda aBOTTOM_CENTERo reduzca el tamaño de fuente y el relleno a la mitad. Una alternativa es colocar la leyenda fuera del mapa como un bloque estático y sincronizar el estado mediante JavaScript. ParaBOTTOM_CENTER, simplemente cambie la posición dentro debuildLegend().
¿Cuántos marcadores puede manejar el mapa sin problemas de rendimiento?
Hasta 100 marcadores en pantalla no causan retraso perceptible, incluso en smartphones de gama media. De 100 a 500 empiezan a notarse demoras al hacer zoom y desplazarse. Para más de 500 active el agrupamiento; Google proporciona la librería lista para usar
@googlemaps/markerclusterer, que agrupa los marcadores cercanos en círculos numerados. En la práctica: 50 marcadores con iconos PNG de 2 KB suponen 100 KB de descarga y renderizado instantáneo. 500 marcadores sin agrupamiento implican que el mapa realiza unas 500 llamadas adrawImage()por cada fotograma de zoom, lo cual se vuelve perceptible.
Qué hacer si los marcadores no se muestran
Primero, revise la consola del navegador. Errores típicos: MissingKeyMapError (clave de API no proporcionada), RefererNotAllowedMapError (dominio no incluido en la lista de permitidos de la clave), Cannot read property 'maps' of undefined (el script de la API no se cargó; revise la URL y el callback). Abra DevTools en la pestaña Consola y actualice la página; la línea roja de error mostrará inmediatamente la causa raíz.
Segundo, asegúrese de que el contenedor #mapCanvas existe en el DOM cuando se llama a drawMap(). Si su script está en <head> sin defer, el elemento aún no se ha renderizado y el mapa falla silenciosamente. Los atributos [async defer](/orig_post/luchshij-sposob-zagruzit-vneshnij-javascript) en la etiqueta <script> solucionan esto, pero la práctica recomendada es colocar la etiqueta <script> al final de <body>, después de todos los elementos del DOM.
Tercero, verifique las rutas de los iconos. Si marker_exact.png no está en images/ relativo al archivo HTML, el navegador devuelve un 404 para la imagen y el marcador se muestra como el pin rojo estándar (o no se muestra en absoluto, según la versión de la API). Consejo: verifique siempre las rutas mediante la pestaña Red en DevTools filtrando por tipo Img; los iconos rotos aparecerán resaltados en rojo inmediatamente.
Lo más importante: pruebe siempre con una clave real. Sin clave o con una clave no válida, el mapa se oscurece con una marca de agua de «Solo para fines de desarrollo» y algunos métodos de la API devuelven respuestas vacías. Tuvimos un caso en el que el mapa funcionaba perfectamente en staging pero la leyenda no se mostraba en producción: resultó que la clave de producción estaba restringida por IP y la API rechazaba los controles silenciosamente. Revise las restricciones de la clave en Cloud Console antes de desplegar.
Cubrimos el ciclo completo: desde la obtención de una clave hasta un mapa en vivo con leyenda. Probamos el código anterior en Google Maps API v3.56 (versión actual a junio de 2026); los cinco marcadores con iconos personalizados se renderizaron correctamente y la leyenda en RIGHT_TOP no se superpuso a los controles de zoom.
Si va más allá, tenga en cuenta tres cosas. Primero: cargue siempre los iconos al tamaño al que aparecerán en el mapa, ya que el redimensionamiento sobre la marcha por parte del navegador cuesta fotogramas durante el zoom. Segundo: para mapas con docenas de marcadores, el agrupamiento es obligatorio; de lo contrario, los clientes móviles se lo agradecerán con retrasos. Tercero: mantenga la clave de API bajo restricción de referencia HTTP desde el primer día, incluso en entornos de desarrollo; una clave filtrada sin restricciones es un camino directo a que el tráfico de otra persona corra por su cuenta de facturación.
Si la tarea se vuelve más compleja (agrupamiento, ventanas de información al hacer clic, filtrado de marcadores por categorías), háganos saber en los comentarios qué escenario cubrir a continuación.



