MapLibre-Geoman with Vite: draw and export GeoJSON

This starter uses Vite, MapLibre GL JS and MapLibre-Geoman Free to draw and edit GeoJSON. It needs no account or basemap token. The map starts with a blank style so you can focus on the drawing tools; add your preferred basemap style for a production application.
Create the Vite application
npm create vite@latest maplibre-geoman-vite -- --template vanilla
cd maplibre-geoman-vite
npm install
npm install [email protected] @geoman-io/[email protected]
This guide uses JavaScript. For TypeScript projects, the package provides types; use the installation reference for your installed release's options and event types.
Initialize the map and Geoman
Create src/geoman-editor.js. This exact module is exercised in our browser example checks.
import * as ml from "maplibre-gl"
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url"
import { Geoman } from "@geoman-io/maplibre-geoman-free"
import "maplibre-gl/dist/maplibre-gl.css"
import "@geoman-io/maplibre-geoman-free/dist/maplibre-geoman.css"
ml.setWorkerUrl(workerUrl)
export async function createEditor(
container,
initialData = { type: "FeatureCollection", features: [] },
) {
const map = new ml.Map({
container,
center: [0, 0],
zoom: 2,
// A blank map works without a token. Substitute your own basemap style.
style: { version: 8, sources: {}, layers: [] },
})
const geoman = new Geoman(map)
await new Promise((resolve) => geoman.mapAdapter.once("gm:loaded", resolve))
await geoman.features.importGeoJson(initialData)
return { map, geoman, getData: () => geoman.features.exportGeoJson() }
}
The separate worker import matters for MapLibre GL JS 6 with Vite. gm:loaded signals that Geoman is ready for feature imports and draw-mode calls; it is different from the map's own load event. mapAdapter provides Geoman event typing when you move this code to TypeScript.
Add a container and export control
Replace src/main.js with:
import { createEditor } from "./geoman-editor.js"
import "./style.css"
const app = document.querySelector("#app")
app.innerHTML = `
<h1>Draw and edit GeoJSON</h1>
<div id="map"></div>
<button id="export" disabled>Export GeoJSON</button>
<pre id="output"></pre>
`
const editor = await createEditor("map")
const exportButton = document.querySelector("#export")
exportButton.disabled = false
exportButton.addEventListener("click", () => {
document.querySelector("#output").textContent = JSON.stringify(
editor.getData(),
null,
2,
)
})
Replace src/style.css with:
body {
margin: 0;
font-family: system-ui, sans-serif;
}
#app {
padding: 16px;
}
#map {
height: 65vh;
min-height: 320px;
}
button {
margin-top: 12px;
padding: 8px 16px;
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
Run npm run dev, choose the polygon tool, draw an area and click Export GeoJSON. Call editor.map.remove() when removing this map from a longer-lived application. Framework components should do that in their unmount cleanup.
Import existing features
Pass a GeoJSON feature collection as the second argument to createEditor. The function imports it after Geoman finishes initializing. Keep Geoman's feature properties when exporting and re-importing data. See the Features API for supported shapes and metadata.
Common setup issues
- Worker loading errors: keep the Vite worker URL import and
setWorkerUrlcall before constructing the map. - Unstyled controls: load both MapLibre and Geoman CSS.
- Blank map area: set an explicit height. This example deliberately has no basemap tiles.
- Tool calls do nothing: wait for
gm:loadedbefore enabling a drawing mode.
Explore React, Vue, Next.js and other examples, try the full MapLibre demo, or compare Free and Pro.
Want more geo-editing insights?
Get tips, tutorials, and product updates delivered to your inbox.
No spam. Unsubscribe anytime.
