Skip to content

Format v2

Format v2 gives a maplibre-yaml document one new idea: a visible line between the parts of your map that become a MapLibre style.json and the parts that only exist while the map is running. You author that line yourself, at the top level, with two sections — style: and runtime:.

v2 is additive and opt-in. A v2 document and its v1 twin parse to the deep-equal internal model and render and emit identically. This is the AE2 guarantee: adopting v2 requires no migration, and nothing you have already written in v1 breaks. You move to v2 when the split earns its keep — not because a version bump forced your hand.

Here is one map, authored both ways. They produce the same model, so <ml-map> renders them indistinguishably.

version: 2
type: map
id: parcels
style:
basemap: https://demotiles.maplibre.org/style.json
center: [-73.98, 40.75]
zoom: 11
sources:
sites:
type: geojson
url: https://example.com/sites.geojson
runtime:
refresh:
refreshInterval: 30000
layers:
- id: sites
type: circle
source: sites
paint:
circle-radius: 8
circle-color: "#dc2626"
runtime:
label: "Sites"
toggleable: true
runtime:
controls:
navigation: true

The only thing that differs is where the keys sit. In v1 the live-data keys are inline on the source and the experience keys are inline on the layer; in v2 they are grouped under a nested runtime:. The map is the same map. This equivalence is not a claim — it is checked by an end-to-end test that renders a full v2 document through <ml-map>, renders its v1 twin beside it, and asserts the two models are deep-equal (examples/verification/v2/v2-document.html, driven by e2e/v2-parser.spec.ts).

Every v2 document states version: 2 and type: map at its root, then splits into two sections.

Everything under style: contributes to the compiled MapLibre style, possibly by transformation. It carries:

  • basemap — the base style URL (or inline style object) that is merged in at compile time. This is v2’s name for v1’s mapStyle.
  • the cameracenter, zoom, pitch, bearing, lifted to the style: root because they are style-spec root properties.
  • sources — your data sources, each keyed by name.
  • layers — your layer list, in draw order.
  • state — spec-native runtime-tunable values (read by the global-state expression), and optional metadata.

Everything under runtime: is the behavior layer. None of it reaches the compiled style.json — when you eject to a plain style, it is stripped. It carries:

  • map — MapLibre Map constructor options (minZoom, maxZoom, scrollZoom, interactive, maxBounds, and the rest).
  • controls — navigation, scale, geolocate, fullscreen, attribution.
  • legend — the map-level legend.
  • parameters — presentation metadata for your state keys (label, type, range) that a control UI needs but the style spec has nowhere to put.
  • container — the map element’s own style and className.
version: 2
type: map
id: chrome-demo
style:
basemap: https://demotiles.maplibre.org/style.json
layers: []
runtime:
map:
minZoom: 4
maxZoom: 16
controls:
navigation: true
scale: true
legend:
title: "Legend"
position: top-left
container:
style: "height: 480px;"
className: "map-embed"

The split is what makes the eject guarantee legible: you can tell what will survive a compile to style.json by looking at which half a key lives in, with no need to consult a table.

Per-source runtime: live data stays beside its source

Section titled “Per-source runtime: live data stays beside its source”

A source’s live-data configuration — polling refresh, stream, cache, loading — nests under that source’s own runtime: key. It sits right beside the spec fields it acts on, so a source is still one thing to edit.

version: 2
type: map
id: live-quakes
style:
basemap: https://demotiles.maplibre.org/style.json
sources:
quakes:
type: geojson
url: https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson
runtime:
refresh:
refreshInterval: 30000
cache:
enabled: true
ttl: 300000
layers:
- id: quakes
type: circle
source: quakes
paint:
circle-radius: 6
circle-color: "#ef4444"

The spec half of the source (type, url, data, cluster, …) compiles into style.json; the nested runtime: block degrades away on eject.

Per-layer runtime: interactions, legend, label, toggle

Section titled “Per-layer runtime: interactions, legend, label, toggle”

A layer’s experience keys — interactive, legend, label, toggleable — nest under the layer’s own runtime:. The paint, layout, filter, type, and source stay exactly where they are in v1 (they are byte-identical to v1) and compile straight through.

version: 2
type: map
id: interactive-sites
style:
basemap: https://demotiles.maplibre.org/style.json
sources:
sites:
type: geojson
url: https://example.com/sites.geojson
layers:
- id: sites
type: circle
source: sites
paint:
circle-radius: 8
circle-color: "#dc2626"
runtime:
label: "Sites"
toggleable: true
legend:
color: "#dc2626"
label: "Points of interest"
shape: circle
interactive:
click:
popup:
- p:
- str: "A point of interest"

state: is spec-native and compiles through, so it belongs under style:. Its presentation metadata does not compile through, so it lives under runtime.parameters:, keyed by the same state name.

version: 2
type: map
id: parameterized
style:
basemap: https://demotiles.maplibre.org/style.json
state:
scenario:
default: baseline
layers: []
runtime:
parameters:
scenario:
label: "Scenario"
type: enum
values: [baseline, buildout]

If you know a v1 document, this table is the whole migration. It is a relocation, not a rewrite — every value keeps its meaning and its spelling (except the one rename, mapStylebasemap).

v1 locationv2 location
config.mapStylestyle.basemap (renamed)
config.center / zoom / pitch / bearingstyle.center / zoom / pitch / bearing
config.minZoom / maxZoom / maxBounds / minPitch / maxPitchruntime.map.*
config.interactive / scrollZoom / boxZoom / dragRotate / dragPan / keyboard / …runtime.map.*
config.hash / attributionControl / logoPosition / trackResizeruntime.map.*
top-level sourcesstyle.sources
source refresh / stream / cache / loadingsource runtime.*
top-level layersstyle.layers
layer interactive / legend / label / toggleablelayer runtime.*
top-level controlsruntime.controls
top-level legendruntime.legend
block style: / className:runtime.container.style / runtime.container.className
top-level statestyle.state (or document root)
top-level parametersruntime.parameters (or document root)