Custom Marker Icons
Replace default marker pins with custom PNG or SVG icons Each library has its own approach: here's how they compare.
Quick Recommendations
Leaflet — The right choice when you need pixel-perfect control over icon anchor points, shadow images, and retina variants via L.icon() options.
MapLibre GL JS — Makes the most sense when custom icons should be GPU-rendered as symbol layers for thousands of points, or when you need rotation and scaling expressions.
OpenLayers — A strong fit when custom icons need to participate in hit detection, clustering, and feature-level style functions.
MapTiler SDK — The MapTiler SDK take: MapLibre's custom HTML marker approach, with simpler initialization.
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 | 26 | 23 | 28 | 24 |
Code Snippets
Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.
Leaflet26 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);
const svgIcon = L.icon({
iconUrl: 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="0 0 30 40"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.3 15 25 15 25s15-13.7 15-25C30 6.7 23.3 0 15 0z" fill="#e63946"/><circle cx="15" cy="14" r="6" fill="#fff"/></svg>'),
iconSize: [30, 40],
iconAnchor: [15, 40],
popupAnchor: [0, -40]
});
L.marker([48.8584, 2.2945], { icon: svgIcon }).addTo(map).bindPopup('Eiffel Tower — SVG icon');
const htmlIcon = L.divIcon({
html: '<div style="background:#2196F3;color:#fff;border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-weight:bold;border:2px solid #fff;">L</div>',
iconSize: [32, 32],
iconAnchor: [16, 16],
className: ''
});
L.marker([48.8606, 2.3376], { icon: htmlIcon }).addTo(map).bindPopup('Louvre — HTML divIcon');
const greenPin = L.icon({
iconUrl: 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="0 0 30 40"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.3 15 25 15 25s15-13.7 15-25C30 6.7 23.3 0 15 0z" fill="#4CAF50"/><circle cx="15" cy="14" r="6" fill="#fff"/></svg>'),
iconSize: [30, 40],
iconAnchor: [15, 40],
popupAnchor: [0, -40]
});
L.marker([48.8530, 2.3499], { icon: greenPin }).addTo(map).bindPopup('Notre-Dame — green SVG icon');MapLibre GL JS23 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
});
const makeSvgEl = (color) => {
const el = document.createElement('div');
el.innerHTML = `<svg width="30" height="40" viewBox="0 0 30 40" xmlns="http://www.w3.org/2000/svg"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.3 15 25 15 25s15-13.7 15-25C30 6.7 23.3 0 15 0z" fill="${color}"/><circle cx="15" cy="14" r="6" fill="#fff"/></svg>`;
el.style.cursor = 'pointer';
return el;
};
new maplibregl.Marker({ element: makeSvgEl('#e63946') })
.setLngLat([14.4205, 50.0875])
.setPopup(new maplibregl.Popup({ offset: 25 }).setHTML('<b>Old Town Square</b>'))
.addTo(map);
const badgeEl = document.createElement('div');
badgeEl.style.cssText = 'background:#2196F3;color:#fff;border-radius:50%;width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-weight:bold;font-size:14px;border:2px solid #fff;cursor:pointer;';
badgeEl.textContent = 'C';
new maplibregl.Marker({ element: badgeEl })
.setLngLat([14.4006, 50.0904])
.setPopup(new maplibregl.Popup({ offset: 25 }).setHTML('<b>Prague Castle</b>'))
.addTo(map);OpenLayers28 linesShow code
var svgPin = function(color) {
return 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="0 0 30 40"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.3 15 25 15 25s15-13.7 15-25C30 6.7 23.3 0 15 0z" fill="' + color + '"/><circle cx="15" cy="14" r="6" fill="#fff"/></svg>');
};
var features = [
new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([-0.1276, 51.5074])), name: 'Big Ben', color: '#e63946' }),
new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([-0.1015, 51.5014])), name: 'London Eye', color: '#2196F3' }),
new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([-0.0763, 51.5081])), name: 'Tower of London', color: '#4CAF50' })
];
var vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({ features: features }),
style: function(feature) {
return new ol.style.Style({
image: new ol.style.Icon({
src: svgPin(feature.get('color')),
anchor: [0.5, 1],
scale: 1
})
});
}
});
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([-0.1, 51.505]), zoom: 14 })
});MapTiler SDK24 linesShow code
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
container: 'map',
style: maptilersdk.MapStyle.STREETS,
center: [12.4964, 41.9028],
zoom: 13
});
const makeSvgEl = (color) => {
const el = document.createElement('div');
el.innerHTML = `<svg width="30" height="40" viewBox="0 0 30 40" xmlns="http://www.w3.org/2000/svg"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.3 15 25 15 25s15-13.7 15-25C30 6.7 23.3 0 15 0z" fill="${color}"/><circle cx="15" cy="14" r="6" fill="#fff"/></svg>`;
el.style.cursor = 'pointer';
return el;
};
new maptilersdk.Marker({ element: makeSvgEl('#9C27B0') })
.setLngLat([12.4922, 41.8902])
.setPopup(new maptilersdk.Popup({ offset: 25 }).setHTML('<b>Colosseum</b>'))
.addTo(map);
const badgeEl = document.createElement('div');
badgeEl.style.cssText = 'background:#e63946;color:#fff;border-radius:50%;width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-weight:bold;font-size:14px;border:2px solid #fff;cursor:pointer;';
badgeEl.textContent = 'V';
new maptilersdk.Marker({ element: badgeEl })
.setLngLat([12.4534, 41.9029])
.setPopup(new maptilersdk.Popup({ offset: 25 }).setHTML('<b>Vatican City</b>'))
.addTo(map);Things to Know
Implementation-specific details that matter for custom marker icons.
L.icon() takes iconUrl, iconSize, iconAnchor, and popupAnchor; L.divIcon() allows full HTML/CSS markers but is DOM-based.
Two approaches: DOM-based Marker with custom HTML element, or GPU-rendered symbol layer with loadImage(), which scales better.
ol.style.Icon accepts src URL and anchor offsets; SVG icons need explicit size since OpenLayers cannot auto-detect SVG dimensions.
Uses the same Marker({ element }) API as MapLibre; you pass a custom DOM element as the marker content.
Which Library to Use?
Replace the default marker pin with custom PNG or SVG icons using L.icon() and L.divIcon().
The right choice when you need pixel-perfect control over icon anchor points, shadow images, and retina variants via L.icon() options.
Use custom HTML elements or symbol layers with image sprites for marker icons.
Makes the most sense when custom icons should be GPU-rendered as symbol layers for thousands of points, or when you need rotation and scaling expressions.
Style point features with custom icons using ol.style.Icon with SVG or PNG sources.
A strong fit when custom icons need to participate in hit detection, clustering, and feature-level style functions.
Create markers with custom HTML elements or SVG icons using the MapTiler SDK marker API.
The MapTiler SDK take: MapLibre's custom HTML marker approach, with simpler initialization.
Build Maps with AI
Generate code like these examples using mapping library skills in your IDE.