MapSnippets logoMapSnippets logo

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

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 lines26232824

Code Snippets

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

Leaflet26 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);
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
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
});
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
arrow_forwardFull example
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: '&copy; <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
arrow_forwardFull example
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.

Leaflet

L.icon() takes iconUrl, iconSize, iconAnchor, and popupAnchor; L.divIcon() allows full HTML/CSS markers but is DOM-based.

MapLibre GL JS

Two approaches: DOM-based Marker with custom HTML element, or GPU-rendered symbol layer with loadImage(), which scales better.

OpenLayers

ol.style.Icon accepts src URL and anchor offsets; SVG icons need explicit size since OpenLayers cannot auto-detect SVG dimensions.

MapTiler SDK

Uses the same Marker({ element }) API as MapLibre; you pass a custom DOM element as the marker content.

Which Library to Use?

auto_awesome

Build Maps with AI

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