Skip to content
Add an Icon to the Map

Add an Icon to the Map in Maptoolkit Maps JS

Adding a custom icon to a Maptoolkit map involves loading an image into the map’s sprite with addImage, then referencing it by name in a symbol layer’s icon-image paint property. The icon is rendered at the coordinates of each feature in a GeoJSON point source. Use this pattern to mark locations with branded pins, category icons, or any custom SVG or PNG asset.

const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', async () => {
        const image = await map.loadImage('https://maplibre.org/maplibre-gl-js/docs/assets/custom_marker.png');
        map.addImage('custom-marker', image.data);

        map.addSource('points', {
                type: 'geojson',
                data: {
                    type: 'FeatureCollection',
                    features: [
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [11.39085, 47.27574] }, properties: { title: 'Innsbruck' } },
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [13.0550, 47.8095] }, properties: { title: 'Salzburg' } },
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [16.3738, 48.2082] }, properties: { title: 'Vienna' } }
                    ]
                }
            });

            map.addLayer({
                id: 'symbols',
                type: 'symbol',
                source: 'points',
                layout: {
                    'icon-image': 'custom-marker'
                }
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Add an Icon to the Map - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Load a custom icon image and use it as a symbol layer." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
    </style>
</head>
<body>
<div id="map"></div>
<script>
    const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', async () => {
        const image = await map.loadImage('https://maplibre.org/maplibre-gl-js/docs/assets/custom_marker.png');
        map.addImage('custom-marker', image.data);

        map.addSource('points', {
                type: 'geojson',
                data: {
                    type: 'FeatureCollection',
                    features: [
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [11.39085, 47.27574] }, properties: { title: 'Innsbruck' } },
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [13.0550, 47.8095] }, properties: { title: 'Salzburg' } },
                        { type: 'Feature', geometry: { type: 'Point', coordinates: [16.3738, 48.2082] }, properties: { title: 'Vienna' } }
                    ]
                }
            });

            map.addLayer({
                id: 'symbols',
                type: 'symbol',
                source: 'points',
                layout: {
                    'icon-image': 'custom-marker'
                }
        });
    });
</script>
</body>
</html>

How it works

Two steps that are easy to collapse and should not be. loadImage fetches the image, addImage registers it in the map’s sprite under a name, and only then can a symbol layer reference that name through icon-image. Referencing a name that was never added renders nothing and logs nothing.

loadImage is asynchronous, which is why the handler is async and awaits it. Adding the layer before the image finishes loading gives you an empty symbol layer.

Icons are part of the style, not the data, so the same image can back thousands of points at no extra cost. That is the difference from Marker, where every point is a DOM element.

icon-allow-overlap decides whether icons may collide. Left at its default, the renderer hides symbols that would overlap, which looks like missing data when points are dense.

Next steps

One icon for everything is rarely enough. Choosing the image per feature from a property is the next step: several images registered up front and selected between in icon-image, so a single layer covers every category of point you have. It is the same expression syntax that drives colour from a property.

Labels usually come with icons, and how they are cased and spaced matters as much as the icon does. The same symbol layer draws text, so the name sits under the pin without a second layer, and the offset and anchor properties are what keep the two from colliding.