Create a Draggable Point in Maptoolkit Maps JS
A draggable point rendered as a GeoJSON circle layer is more customizable than a Marker because you control the appearance entirely through layer paint properties. The interaction is built by listening to mousedown on the layer, then tracking mousemove on the map to update the source coordinates, and releasing on mouseup. Use this approach when you need a draggable point that matches your map’s visual style or needs to interact with other GeoJSON features.
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');
const geojson = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [11.39085, 47.27574] }
}]
};
map.on('load', () => {
map.addSource('point', { type: 'geojson', data: geojson });
map.addLayer({
id: 'point',
type: 'circle',
source: 'point',
paint: { 'circle-radius': 10, 'circle-color': '#3887be' }
});
let isDragging = false;
map.on('mousedown', 'point', (e) => {
e.preventDefault();
isDragging = true;
map.getCanvas().style.cursor = 'grabbing';
});
map.on('mousemove', (e) => {
if (!isDragging) return;
geojson.features[0].geometry.coordinates = [e.lngLat.lng, e.lngLat.lat];
map.getSource('point').setData(geojson);
document.getElementById('coords').textContent = `${e.lngLat.lng.toFixed(5)}, ${e.lngLat.lat.toFixed(5)}`;
});
map.on('mouseup', () => {
if (!isDragging) return;
isDragging = false;
map.getCanvas().style.cursor = '';
});
map.on('mouseleave', 'point', () => {
if (!isDragging) map.getCanvas().style.cursor = '';
});
map.on('mouseenter', 'point', () => {
if (!isDragging) map.getCanvas().style.cursor = 'grab';
});
});<!DOCTYPE html>
<html lang="en">
<head>
<title>Create a Draggable Point - Maptoolkit Maps JS</title>
<meta property="og:description" content="Create a draggable GeoJSON point on a map 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%; }
#coords {
position: absolute;
bottom: 30px;
left: 10px;
background: white;
padding: 8px 14px;
border-radius: .4rem;
font-family: sans-serif;
font-size: 13px;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="coords">Drag the point</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');
const geojson = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [11.39085, 47.27574] }
}]
};
map.on('load', () => {
map.addSource('point', { type: 'geojson', data: geojson });
map.addLayer({
id: 'point',
type: 'circle',
source: 'point',
paint: { 'circle-radius': 10, 'circle-color': '#3887be' }
});
let isDragging = false;
map.on('mousedown', 'point', (e) => {
e.preventDefault();
isDragging = true;
map.getCanvas().style.cursor = 'grabbing';
});
map.on('mousemove', (e) => {
if (!isDragging) return;
geojson.features[0].geometry.coordinates = [e.lngLat.lng, e.lngLat.lat];
map.getSource('point').setData(geojson);
document.getElementById('coords').textContent = `${e.lngLat.lng.toFixed(5)}, ${e.lngLat.lat.toFixed(5)}`;
});
map.on('mouseup', () => {
if (!isDragging) return;
isDragging = false;
map.getCanvas().style.cursor = '';
});
map.on('mouseleave', 'point', () => {
if (!isDragging) map.getCanvas().style.cursor = '';
});
map.on('mouseenter', 'point', () => {
if (!isDragging) map.getCanvas().style.cursor = 'grab';
});
});
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
This draws the point as a circle layer over a GeoJSON source rather than using a Marker,
which means appearance comes from paint properties and the point is rendered in WebGL along
with everything else. The trade-off is that dragging is no longer free: a Marker has
draggable: true, a layer does not.
The drag is assembled from three map events. mousedown on the layer starts it and calls
preventDefault so the map does not pan underneath, mousemove writes the new coordinate
into the source with setData, and mouseup ends it and removes the temporary listeners.
Updating through setData is the part to copy: you change the data and the renderer redraws.
There is no per-feature move API, and reaching into rendered geometry does not work.
Removing the mousemove listener on mouseup is not optional. Left attached, the point
follows the cursor forever after the first drag.
Next steps
Styling is the reason to choose a layer over a marker, so the next step is using it: size or colour driven from the data, a different look while dragging, a halo when the point is selected.
More than one draggable point is the usual next requirement, which means tracking which one is being dragged rather than assuming a single feature. Two of them plus the Routing API is a draggable from-and-to interface.