Maplibre Plugin For Creating And Editing Geometry Layers
Draw, Edit, Drag, Cut, Rotate, Split, Scale, Measure, Snap and Pin Layers
Supports Markers, CircleMarkers, Polylines, Polygons, Circles, Rectangles, ImageOverlays, LayerGroups, GeoJSON, MultiLineStrings and MultiPolygons
Visit geoman.io/docs/maplibre to get started. For contributor-focused internals, see ARCHITECTURE.md.
Adding your own buttons to the control bar is documented in docs/custom-controls.md.
If you have support questions or want to report issues, please create an issue in this repository. Use this repository for issues related to both the free and pro versions of Maplibre-Geoman.
Check out the full power of Maplibre-Geoman Pro on geoman.io/demo/maplibre
Or watch a demo video on YouTube
# install maplibre free version
npm install @geoman-io/maplibre-geoman-free
# install mapbox free version
npm install @geoman-io/mapbox-geoman-freeAdd the following content to .npmrc in your project root
#.npmrc
@geoman-io:registry=https://npm.geoman.io/
//npm.geoman.io/:_authToken="<YOUR LICENSE KEY>"Replace <YOUR LICENSE KEY> with your license key.
# install pro version
npm install @geoman-io/maplibre-geoman-proDon't have a license key yet? Purchase one here.
| Framework/Template | Demo URL | Code URL | Description |
|---|---|---|---|
| maplibre-geoman-vite | Demo | Code | Vanilla JavaScript implementation using Vite as the build tool |
| maplibre-geoman-vue | Demo | Code | Vue.js integration showcasing reactive map editing capabilities |
| maplibre-geoman-react | Demo | Code | React implementation with hooks and components for map editing |
| maplibre-geoman-preact | Demo | Code | Lightweight Preact alternative to the React implementation |
| maplibre-geoman-nextjs | Demo | Code | Next.js integration with server-side rendering support |
| maplibre-geoman-svelte | Demo | Code | Svelte implementation offering reactive map editing features |
<!-- index.html -->
<html lang="en_US">
<head>
<title>Geoman Maplibre</title>
<style>
#dev-map {
height: 100vh;
width: 100vw;
}
</style>
</head>
<body>
<div id="dev-map"></div>
</body>
</html>@geoman-io/maplibre-geoman-free requires MapLibre GL JS v6 (maplibre-gl >=6.0.0 <7.0.0)
and is ESM-only, following MapLibre v6 itself. There is no UMD build and no require entry
point. (@geoman-io/mapbox-geoman-free is unaffected and still ships both.)
MapLibre v6 no longer resolves its own web worker once the library is bundled, so your app must point it at one before creating a map β this is a MapLibre requirement, not a Geoman one, and a map created without it fails at runtime. How you get the URL depends on your bundler; see MapLibre's bundler guide.
// Vite
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
ml.setWorkerUrl(workerUrl);
// Webpack 5 / Rspack (or any bundler supporting `new URL(..., import.meta.url)`)
ml.setWorkerUrl(new URL("maplibre-gl/dist/maplibre-gl-worker.mjs", import.meta.url).href);
// No bundler β serve the file yourself and pass its path
ml.setWorkerUrl("/vendor/maplibre-gl-worker.mjs");import * as ml from "maplibre-gl";
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
import { Geoman, type GmOptionsPartial } from "@geoman-io/maplibre-geoman-free";
import "maplibre-gl/dist/maplibre-gl.css";
import "@geoman-io/maplibre-geoman-free/dist/maplibre-geoman.css";
// See "MapLibre requirements" above β must run before the first `new ml.Map()`.
ml.setWorkerUrl(workerUrl);
const mapStyle: ml.StyleSpecification = {
version: 8,
glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
sources: {
"osm-tiles": {
type: "raster",
tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"],
tileSize: 256,
attribution: "Β© OpenStreetMap contributors",
},
},
layers: [
{
id: "osm-tiles-layer",
type: "raster",
source: "osm-tiles",
minzoom: 0,
maxzoom: 19,
},
],
};
const map = new ml.Map({
container: "dev-map",
style: mapStyle,
center: [0, 51],
zoom: 5,
});
const gmOptions: GmOptionsPartial = {
// geoman options here
};
const geoman = new Geoman(map, gmOptions);
map.on("gm:loaded", () => {
console.log("Geoman fully loaded");
// Here you can add your geojson shapes for example
const shapeGeoJson = {
type: "Feature",
geometry: { type: "Point", coordinates: [0, 51] },
};
map.gm.features.addGeoJsonFeature({ shapeGeoJson });
});import mapboxgl from "mapbox-gl";
import { Geoman, type GmOptionsPartial } from "@geoman-io/mapbox-geoman-free";
import "mapbox-gl/dist/mapbox-gl.css";
import "@geoman-io/mapbox-geoman-free/dist/mapbox-geoman.css";
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_TOKEN ?? "";
const mapStyle: mapboxgl.StyleSpecification = {
version: 8,
glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
sources: {
"osm-tiles": {
type: "raster",
tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"],
tileSize: 256,
attribution: "Β© OpenStreetMap contributors",
},
},
layers: [
{
id: "osm-tiles-layer",
type: "raster",
source: "osm-tiles",
minzoom: 0,
maxzoom: 19,
},
],
};
const map = new mapboxgl.Map({
container: "dev-map",
style: mapStyle,
center: [0, 51],
zoom: 5,
});
const gmOptions: GmOptionsPartial = {
// geoman options here
};
const geoman = new Geoman(map, gmOptions);
await geoman.waitForGeomanLoaded();Add your own buttons to the control bar with a custom icon, tooltip and click
handler. Each runs its own onClick instead of toggling a built-in mode β handy
for toggling a map layer, exporting data, or any host-specific action.
const geoman = new Geoman(map, {
customControls: [
{
id: 'toggle-parcels', // unique; also used for the button id/class and removal
title: 'Toggle parcels layer', // tooltip (first 2 chars are the text fallback)
icon: '<svg viewBox="0 0 20 20">...</svg>', // optional SVG, sanitized before render
order: 10, // optional sort order within the custom group
onClick: ({ gm, control, event }) => {
const visible = map.getLayoutProperty('parcels', 'visibility') !== 'none';
map.setLayoutProperty('parcels', 'visibility', visible ? 'none' : 'visible');
},
},
],
});You can also add or remove controls at run time:
geoman.control.addCustomControl({ id: 'export', title: 'Export', onClick: () => save() });
geoman.control.removeCustomControl('export');See docs/custom-controls.md for the full reference and
a live example in the dev app (apps/dev/common.ts).
Create a marker, circle, or rectangle from form values with gm.draw.create(input),
or commit the current preview with gm.draw.finish(input?). See
the API guide for examples, validation, and events.
We welcome contributions from the community! Please read our CONTRIBUTING.md for guidelines on how to get started, report issues, and submit pull requests.
# Install the Node.js and pnpm versions declared in mise.toml
mise install
# Install dependencies (this repo uses pnpm)
pnpm install --frozen-lockfile
# Run dev app with MapLibre adapter
pnpm run dev:maplibre
# Run dev app with Mapbox adapter
pnpm run dev:mapbox
# Run checks
pnpm run workspace:validate
pnpm run lint:all
pnpm run typecheck:all
pnpm run build:all
# Run smoke tests for each variant
pnpm run test:maplibre --project=chromium --grep="@smoke"
pnpm run test:mapbox --project=chromium --grep="@smoke"
# Run full test suite for both variants
pnpm run test:allWe are committed to fostering a welcoming and respectful community. Please read our Code of Conduct before participating.
If you discover a security vulnerability, please see our Security Policy for responsible disclosure guidelines.
This repository is licensed under the MIT License, which means you are free to use, modify, and distribute this software for any purpose, including commercial use. The free version of Maplibre-Geoman is fully open source. However, please note that the Pro version of Maplibre-Geoman is not open source and requires a commercial license, which can be purchased at geoman.io/pricing. See LICENSE for full license details.
