MapSnippets logoMapSnippets logo

Markers & Popups

Place markers with custom icons and interactive popups Each library has its own approach: here's how they compare.

Quick Recommendations

Leaflet — Ideal when you need quick marker placement with the simplest possible API: one line for marker + popup.

MapLibre GL JS — The right choice when markers are part of a larger data layer or you need GPU-rendered symbols for thousands of points.

OpenLayers — Makes the most sense when markers need to integrate with OGC feature layers and complex geometry operations.

MapTiler SDK — Brings MapLibre's marker API, with zero configuration overhead.

At a Glance

LeafletMapLibreOpenLayersSDK
Coordinates[lat, lng][lng, lat]fromLonLat([lng, lat])[lng, lat]
RenderingDOM + Canvas (2D)WebGL (GPU, 60fps)Canvas + WebGLWebGL (GPU, 60fps)
Bundle size~42 KB gzipped~250 KB gzipped~290 KB gzipped (tree-shakeable)~280 KB gzipped (includes MapLibre)
Learning curveEasiest (simple API, great tutorials)Moderate (source/layer model, expressions)Steepest (projections, OGC standards, verbose API)Easy (MapLibre API with convenience helpers)
3D supportNo native 3D (2D only)Full: terrain, fill-extrusion, globe, skyLimited: WebGL layers, no native globeFull (same as MapLibre)
Code lines8183518

Implementation Details

LeafletMapLibreOpenLayersSDK
Api PatternL.marker([lat, lng]).addTo(map).bindPopup(html)new maplibregl.Marker().setLngLat([lng,lat]).setPopup(popup).addTo(map)ol.Feature + ol.Overlay for popupsnew maptilersdk.Marker() (same as MapLibre)
Custom IconsL.icon() with PNG/SVG URLsCustom HTML element or symbol layer with spritesol.style.Icon with src URLCustom HTML element (same as MapLibre)
Performance At ScaleDegrades >1000 markers (DOM-based)Handles 10K+ via symbol layers (GPU-rendered)Good with WebGL rendererSame as MapLibre

Code Snippets

Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.

Leaflet8 linesShow code
arrow_forwardFull example
const map = L.map('map').setView([48.8566, 2.3522], 13);
L.tileLayer('https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', {
  tileSize: 256,
  attribution: '&copy; <a href="https://www.maptiler.com/">MapTiler</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);
L.marker([48.8584, 2.2945]).addTo(map).bindPopup('<strong>Eiffel Tower</strong><br>Champ de Mars, Paris');
L.marker([48.8606, 2.3376]).addTo(map).bindPopup('<strong>Louvre Museum</strong><br>Rue de Rivoli, Paris');
L.marker([48.8530, 2.3499]).addTo(map).bindPopup('<strong>Notre-Dame</strong><br>Île de la Cité, Paris');
MapLibre GL JS18 linesShow code
arrow_forwardFull example
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.maptiler.com/maps/streets-v4/style.json?key=YOUR_MAPTILER_KEY',
  center: [14.42, 50.08],
  zoom: 13
});
map.addControl(new maplibregl.NavigationControl());
const places = [
  { coords: [14.4205, 50.0875], name: 'Old Town Square', desc: 'Historic center of Prague' },
  { coords: [14.4006, 50.0904], name: 'Prague Castle', desc: 'Largest castle complex' },
  { coords: [14.4141, 50.0814], name: 'Charles Bridge', desc: 'Iconic Gothic bridge' }
];
places.forEach(p => {
  new maplibregl.Marker({ color: '#2196F3' })
    .setLngLat(p.coords)
    .setPopup(new maplibregl.Popup({ offset: 25 }).setHTML(`<b>${p.name}</b><br>${p.desc}`))
    .addTo(map);
});
OpenLayers35 linesShow code
arrow_forwardFull example
var parisFeature = new ol.Feature({
  geometry: new ol.geom.Point(ol.proj.fromLonLat([2.3522, 48.8566])),
  name: 'Paris'
});
var eiffelFeature = new ol.Feature({
  geometry: new ol.geom.Point(ol.proj.fromLonLat([2.2945, 48.8584])),
  name: 'Eiffel Tower'
});
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector({ features: [parisFeature, eiffelFeature] }),
  style: new ol.style.Style({
    image: new ol.style.Circle({ radius: 8, fill: new ol.style.Fill({ color: '#e63946' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) })
  })
});
var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', attributions: '&copy; <a href="https://www.maptiler.com/copyright/">MapTiler</a>' }) }),
    vectorLayer
  ],
  view: new ol.View({ center: ol.proj.fromLonLat([2.33, 48.86]), zoom: 13 })
});
var popupEl = document.getElementById('popup');
var overlay = new ol.Overlay({ element: popupEl, positioning: 'bottom-center', offset: [0, -12] });
map.addOverlay(overlay);
map.on('click', function(evt) {
  var feature = map.forEachFeatureAtPixel(evt.pixel, function(f) { return f; });
  if (feature) {
    popupEl.style.display = 'block';
    popupEl.innerHTML = '<strong>' + feature.get('name') + '</strong>';
    overlay.setPosition(evt.coordinate);
  } else {
    popupEl.style.display = 'none';
  }
});
MapTiler SDK18 linesShow code
arrow_forwardFull example
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
  container: 'map',
  style: maptilersdk.MapStyle.STREETS,
  center: [2.3522, 48.8566],
  zoom: 13
});
const places = [
  { coords: [2.2945, 48.8584], name: 'Eiffel Tower' },
  { coords: [2.3500, 48.8530], name: 'Notre-Dame' },
  { coords: [2.3376, 48.8606], name: 'Louvre Museum' }
];
places.forEach(p => {
  new maptilersdk.Marker({ color: '#9C27B0' })
    .setLngLat(p.coords)
    .setPopup(new maptilersdk.Popup({ offset: 25 }).setHTML(`<b>${p.name}</b>`))
    .addTo(map);
});

Things to Know

Implementation-specific details that matter for markers & popups.

Leaflet

DOM-based markers are easy to style with CSS but performance degrades beyond ~1000 markers; use clustering or canvas renderer for large datasets.

MapLibre GL JS

DOM markers and symbol layers are two different approaches: symbol layers scale better but require the source/layer model.

OpenLayers

Popups use ol.Overlay (a positioned DOM element), not a built-in popup class; more verbose but fully customizable.

MapTiler SDK

Marker and Popup API is identical to MapLibre; the only difference is the namespace (maptilersdk vs maplibregl).

Which Library to Use?

auto_awesome

Build Maps with AI

Generate code like these examples using mapping library skills in your IDE.