map0

Open source · built on MapLibre GL JS · web component

map0

The web map client you configure, not code.

The name is the promise: a complete map client, and zero lines of code to get one. Basemaps, layer tree, legends, popups, search, print — every one of them declared in a single JSON document that fits in a CMS field.

This is the real thing, not a screenshot. Three sources at once — a WMS, a vector tile layer and GeoJSON points from a WFS. Open the layer tree, switch the zoning plan on, click a fountain, try the aerial basemap, search for a street, measure a distance. All of it came out of the single config printed further down this page.

~23 KB gzip on page load, before the map is even in view
1 JSON document per map — validated, no second config file
19 demos running against live public services
2 UI languages built in, every string overridable per locale

Why map0

A map client, not another map library.

MapLibre, OpenLayers and Leaflet render beautifully — but every layer tree, legend, popup and print button is a custom project, rebuilt per website. Geoportals give you all of that and an application you have to host. map0 is the missing middle.

🧩

It is a web component

<map0-viewer> is a standard custom element with its own shadow DOM. Drop it into a CMS field, a Rails view, Next.js, Vue, or a plain HTML file — no wrapper, no version lock-in.

Styles never leak in or out, so it looks the same on every host page, and three maps on one page cost you MapLibre exactly once.

⌨️

Everything is configuration

Every feature is reachable from the JSON config. If it cannot be configured, it does not ship. Basemaps, overlays, popups, legends, print layout, theme, languages: all keys in one document.

That document lives happily in a CMS text field, so the person who maintains the map never needs a developer, a build step or a deployment.

🪶

Light on the page

A page carrying a map0 embed downloads about 23 KB gzip. The engine and MapLibre only arrive when the map approaches the viewport — a map nobody scrolls to costs nothing.

Measuring, printing, coordinate projections and capabilities parsing are separate chunks, fetched the first time somebody actually uses them.

Quick start

A styled basemap and a live WMS overlay, in one file.

Two elements: the script tag that registers the custom element, and the element itself with its configuration. The snippet below is generated from the map beside it, so the two can never drift apart.

What those 20 lines bought: zoom, compass, scale bar, fullscreen, geolocation, globe, attribution, a layer tree with an opacity slider, a legend fetched from the service, clickable features, coordinate readout, measuring, print and export, and a light/dark theme that follows the operating system.

Three ways to hand over the config

In this order of precedence: a config property set from JavaScript (for SPAs and wrappers), a config-src attribute pointing at a JSON file, or an inline <script type="application/json"> child as above — the CMS-friendly one.

Batteries included

What other clients call plugins, map0 calls keys.

Elsewhere half of this list is something you install, wire up and keep compatible. Here every one of them is a key in the config — and the code behind it only downloads the first time somebody uses it. Each card links to a demo running on live services.

🗺️ Every service you already publish

WMS with GetFeatureInfo, WMTS resolved straight from GetCapabilities (dead mirrors included), vector tiles and PMTiles styled with the MapLibre style spec, GeoJSON with clustering.

See the data source demos →

🌳 A layer tree that behaves like one

Groups, visibility, opacity, per-layer status and zoom hints, zoom-to-layer, metadata links back to the catalog — and users adding their own WMS or WMTS by URL, parsed in the browser.

See add layers →

💬 Feature info that reads well

Templates with {{placeholders}}, field tables, hover tooltips, multi-layer hits in one popup, selection highlight — sanitised before it ever hits the DOM.

See popups & hover →

📊 Legends, on screen and on paper

Service legends via GetLegendGraphic, swatches derived from the style itself, or hand-written entries when a service has no legend worth showing.

See legends →

🖨️ Print & export

High-resolution PNG, PDF on real A4/A3 pages, and a composed sheet with title, legend, scale bar, attribution and date — configurable, in the browser, no print server.

See print & export →

🔍 Search that fits your gazetteer

Type-ahead place and address search on Photon or Nominatim, self-hosted or public — or any geocoder of your own via a URL template. Coordinates work as input too.

See search →

📏 Measuring and coordinates

Distance and area on the sphere with draggable vertices, and a right-click readout in WGS 84, Gauss-Krüger and UTM with copy buttons.

See measuring →

🔗 Shareable state, and a globe

The whole map state travels in the URL — view, basemap, visible layers, even services a user added themselves. Globe projection is one config key away.

See share & permalink →

Performance

Nothing loads until the map is on screen.

An embed in a long article should not slow the article down. map0 splits itself into three tiers that arrive at three different moments — and the first tier is checked against a budget in CI, so it stays small.

Page load
~23 KB gz
Map enters the viewport
+ ~304 KB gz
First use of a feature
0.2–138 KB gz each

What that means in practice

The custom element and Lit are all a page carries until a map is nearly in view. The observer starts loading 300 px early, so in normal scrolling the map is ready by the time you see it. Several embeds on one page share the engine, and a map inside a collapsed accordion never loads at all.

Features are chunks

Capabilities parsing, proj4, PMTiles, the print composer and the add-layer dialog are separate files. A map without measuring never downloads the measuring code.

See it in the network panel →

And once it is there, the GPU draws it.

The second kind of fast. MapLibre renders through WebGL 2 — the map is geometry on the graphics card, not a grid of pre-rendered images and not hundreds of DOM nodes.

🎞️

Smooth, not stepped

Zoom is continuous instead of 20 fixed levels, panning stays fluid on a phone, and rotation and tilt cost nothing extra — the same scene, a different camera.

🏷️

Cartography at runtime

Labels are placed, collision-tested and kept upright while the map moves. Colors, widths and filters are style-spec expressions evaluated per feature, so a layer restyles without re-fetching a single tile.

🌍

The globe is free

Switching to a globe is one config key: same data, same style, same renderer — a projection the GPU applies while drawing.

Config-first

One document. Schema-validated. Inheritable.

This is the whole configuration behind the map at the top of this page — fetched from the same file that map loads, so what you read is what runs.



            

No config-file constellation

One JSON document per map. No service registry, no rest-services.json, no config.js next to it. Reuse across many maps is a shared base config referenced with extends, and each map only writes its deltas.

Broken configs say why

A config is validated before anything renders, and the errors appear in place with the JSON path that caused them — an editor can fix it without opening a developer console.

                $.basemaps          at least one basemap is required, e.g. { "type": "style", "url": "…" }
                $.layers[2].url     a "wms" layer needs a "url"
                $.layers[2].layers  a "wms" layer needs "layers" (WMS LAYERS parameter)
              

Themed and translated

A primary color, a radius, a font and light/dark come from the config or from your page's CSS custom properties. The UI ships in German and English, and every single string can be overridden per locale.

Theming →  Languages →  Inheritance →

Where it sits

The missing middle.

Three ways to put a map on a page exist already, and each asks for something you may not want to give: an application to operate, a setup to maintain, or a frontend project to write. map0 is the fourth.

Aspect Geoportal platform Portal framework Map library map0
What it is An application you host A configurable app to set up A rendering engine A component on your page
Embedding An iframe Its own page or an iframe Whatever you build Script tag + one element
Configuration Backend or database Several files and a registry Source code One JSON document
Server needed Yes No, but a build setup No No — static hosting
TOC, legends, print Included Included Build it yourself Included
Who changes the map Someone with server access A developer A developer Anyone with the text field
Look and feel The portal's The framework's Yours to build Themed to your page

Demos

Nineteen demos. Live services. The exact config each one runs.

One topic per page, each against real endpoints. Every demo shows the map, explains what it demonstrates, and prints the configuration that produced it — so the fastest way to judge map0 is to open one.

🚧

Honest status: pre-release

Everything on this page works today against live services, but map0 has not shipped yet. The config schema is v0 and may still change, and there is no npm or CDN package.

Until the release: clone the repository, run pnpm build, and copy packages/ui/dist/ next to your page — it is a flat folder, deployed as a unit, and the embed stays one script tag.