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
| Leaflet | MapLibre | OpenLayers | SDK | |
|---|---|---|---|---|
| Coordinates | [lat, lng] | [lng, lat] | fromLonLat([lng, lat]) | [lng, lat] |
| Rendering | DOM + Canvas (2D) | WebGL (GPU, 60fps) | Canvas + WebGL | WebGL (GPU, 60fps) |
| Bundle size | ~42 KB gzipped | ~250 KB gzipped | ~290 KB gzipped (tree-shakeable) | ~280 KB gzipped (includes MapLibre) |
| Learning curve | Easiest (simple API, great tutorials) | Moderate (source/layer model, expressions) | Steepest (projections, OGC standards, verbose API) | Easy (MapLibre API with convenience helpers) |
| 3D support | No native 3D (2D only) | Full: terrain, fill-extrusion, globe, sky | Limited: WebGL layers, no native globe | Full (same as MapLibre) |
| Code lines | 8 | 18 | 35 | 18 |
Implementation Details
| Leaflet | MapLibre | OpenLayers | SDK | |
|---|---|---|---|---|
| Api Pattern | L.marker([lat, lng]).addTo(map).bindPopup(html) | new maplibregl.Marker().setLngLat([lng,lat]).setPopup(popup).addTo(map) | ol.Feature + ol.Overlay for popups | new maptilersdk.Marker() (same as MapLibre) |
| Custom Icons | L.icon() with PNG/SVG URLs | Custom HTML element or symbol layer with sprites | ol.style.Icon with src URL | Custom HTML element (same as MapLibre) |
| Performance At Scale | Degrades >1000 markers (DOM-based) | Handles 10K+ via symbol layers (GPU-rendered) | Good with WebGL renderer | Same 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
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: '© <a href="https://www.maptiler.com/">MapTiler</a> © <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
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
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: '© <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
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.
DOM-based markers are easy to style with CSS but performance degrades beyond ~1000 markers; use clustering or canvas renderer for large datasets.
DOM markers and symbol layers are two different approaches: symbol layers scale better but require the source/layer model.
Popups use ol.Overlay (a positioned DOM element), not a built-in popup class; more verbose but fully customizable.
Marker and Popup API is identical to MapLibre; the only difference is the namespace (maptilersdk vs maplibregl).
Which Library to Use?
Place L.marker pins on a Leaflet map with interactive popups containing custom HTML content.
Ideal when you need quick marker placement with the simplest possible API: one line for marker + popup.
Place maplibregl.Marker instances on the map with clickable Popup overlays using 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.
Place point features on a vector layer and display popups with ol.Overlay on click in OpenLayers.
Makes the most sense when markers need to integrate with OGC feature layers and complex geometry operations.
Place maptilersdk.Marker instances with Popup information windows on a MapTiler SDK map.
Brings MapLibre's marker API, with zero configuration overhead.
Build Maps with AI
Generate code like these examples using mapping library skills in your IDE.