Skip to content

GeoLeafMapping library for the web

Interactive maps configured through JSON profiles — built on MapLibre GL JS.

Release v3.19.0 2026-10-23 ​

A minor release of @geoleaf/core, published with @geoleaf-plugins/table 1.2.2: GeoLeaf sits better inside a host page — the host's generic rules no longer repaint its fields, its fixed panels stay under the host's dialogs — and a profile that declares its basemaps gets those only.

Highlights:

  • Changed — the built-in basemaps (street, topo, satellite) no longer join a profile's: they serve only a profile that declares none. An integrator who relied on them next to basemaps of their own must now declare them. Without defaultBasemap, the first declared basemap is active.
  • Added — themeable: false on a layer puts it out of the themes' reach: a theme neither hides it for not listing it nor changes it when it lists it.
  • Fixed — a host page's generic rules (select { color: … }) no longer repaint GeoLeaf's fields and the basemap credit: a dark host theme left light text on the layer manager's light panel.
  • Fixed — @geoleaf-plugins/table 1.2.2 translates its layer selector's placeholders, and its panel sits under the host's modal dialogs (--gl-z-table-panel, default 1040).

See the changelog for the complete list.

Release v3.18.0 (2026-10-20) — a minor release of @geoleaf/core, published with five plugins: offline work, downloaded data and archived conflicts stay with the profile — and the source — they belong to, and an integrator chooses the profile a map boots on.

  • Added — profile, an option of GeoLeaf.boot() and GeoLeaf.mount(), decides which profile boots, over the choice the profile switcher stored in the browser.
  • Fixed — a queued write is sent, and a capture is drawn, only under the profile it was made under; an archived conflict is listed and purged only there ({ allProfiles: true } covers every profile). @geoleaf-plugins/offline-ui 1.7.1 exports every profile's pending edits.
  • Fixed — what a download brings belongs to its source: two profiles with a layer of the same id over two sources no longer remove or show each other's downloaded data.
  • Fixed — a profile without the PWA no longer removes the service worker another profile of the same origin uses.
  • Fixed — @geoleaf-plugins/measure 1.0.13 gives the cursor back to the map after a measurement, and @geoleaf-plugins/editor 1.6.2 strokes follow a palette switch.
  • Removed — useSharedCluster, a clustering flag no code read.

Release v3.17.0 (2026-10-18) — a minor release of @geoleaf/core, published with three plugins that now rely on it: a tool that takes over the map's clicks goes through the adapter, and three public subpaths can be imported.

  • Added — exclusive mode on the adapter: IMapAdapter.enterExclusiveMode, exitExclusiveMode and isExclusiveMode. While a tool holds it, a click opens no feature popup and the hover handlers leave the cursor alone. The three members are optional.
  • Changed — MapConfig.padding accepts the object form, and UIConfig.showGeocoding is declared next to the two other toolbar aliases.
  • Changed — @geoleaf-plugins/measure 1.0.12, @geoleaf-plugins/editor 1.6.1 and @geoleaf-plugins/isochrone 1.0.1 enter exclusive mode through the adapter, and require @geoleaf/core 3.17.0 or later.
  • Fixed — capabilities/legend/public-api, capabilities/pwa/public-api and capabilities/filter/engine/index ship their modules: importing a value from them no longer throws ERR_MODULE_NOT_FOUND.
  • Removed — IMapAdapter.createClusterGroup, without a deprecation period. To cluster a layer, pass the cluster option to addGeoJSONLayer.

Release v3.16.3 (2026-10-17) — a patch release of @geoleaf/core alone: map credits are cleaned and shown, and three profile readings are made consistent.

  • Security — map credits are cleaned before they reach the attribution control: a basemap's attribution and a layer's attribution option go through one sanitizer, which keeps inline formatting and safe links and turns any other markup into text.
  • Fixed — a layer's attribution option is shown: it is set on the layer's source.
  • Fixed — a map.padding object naming only some edges frames the map at start-up as a theme view does: each edge it names applies, an edge it does not name is 0.
  • Fixed — the profile schema accepts ui.showPrint, ui.showMeasure and ui.showGeocoding, legacy aliases of modules.<plugin>.showButton the toolbar has always read.
  • Removed — the theme applier's fit on the layers' extent, which could no longer run.

Release v3.16.2 (2026-10-16) — a patch release of @geoleaf/core alone, on switching basemaps: four fixes, no API change.

  • Fixed — a basemap asked while the map's style updates no longer makes MapLibre throw Cannot read properties of undefined (reading 'used') out of its render loop — a theme that shows layers, then a basemap picked at once.
  • Fixed — going back to a vector basemap while another one downloads finishes the switch: geoleaf:basemap:change is emitted, and what the basemap puts on when it lands is put on.
  • Fixed — asking again for the active basemap cancels a switch still waiting for the style: the last call wins.
  • Fixed — a basemap that displays is no longer reported as missing something when the profile loads: tiles alone, type: "maplibre", image, hillshade, wmts and wms are all drawable.

Release v3.16.1 (2026-10-15) — a patch release of @geoleaf/core, published with two fixed plugins and a new one: @geoleaf-plugins/isochrone 1.0.0 shows the zones reachable from a point within a given time.

  • Added — @geoleaf-plugins/isochrone 1.0.0, a new MIT plugin (GeoLeaf.Isochrone, modules.isochrone). It asks a routing engine for the zones reachable from a point of the map, or from the user's position, by car, on foot or by bike, and stacks them in a floating window where they are reordered, recoloured, hidden and removed. The built-in provider asks a Valhalla instance — by default a shared public server with no guarantee, which stops at 60 minutes. A key is never written in a profile: it is given at runtime with configure().
  • Fixed — a write no longer makes MapLibre report the profile's icons as missing: the map waits for the sprite before answering the engine, five seconds at most.
  • Fixed — « Your position » in the routing panel routes from the user's position (@geoleaf-plugins/routing 1.0.4), and a GPS measurement no longer switches off a GPS the user had switched on (@geoleaf-plugins/measure 1.0.11). Both read the core's geolocation state under names it never exposed.

Release v3.16.0 (2026-10-12) — a minor release of @geoleaf/core alone: a layer no longer depends on what its first batch happened to contain, a click on two overlapping layers answers for the one drawn on top, and geoleaf:filters:applied says why the pass ran.

  • Added — geoleaf:filters:applied carries detail.reason: "filter" when the filter was applied, "data" when the same filter is replayed because a layer was written. Do not rewrite a layer in answer to "data": the write replays the pass, and a listener that reloads its data on every pass loops.
  • Fixed — a layer created empty hears a click and a hover on whatever it is filled with afterwards. A layer's gestures are bound to every sub-layer that draws features; on a layer holding several geometries, the feature reported is the one drawn on top.
  • Fixed — between two layers under a click, only the one drawn on top emits geoleaf:feature:click. This removes events: a listener that relied on the click of the layer underneath no longer receives it where another interactive layer covers it.
  • Fixed — a point layer created empty draws its icons and its clusters once filled, and GeoLeaf.GeoJSON.updateLayerData, GeoLeaf.Layers.setData, addFeature and mergeFeatures give the points they write their icon. A layer that ships empty is clustered when it declares a point geometry.
  • Fixed — a layer created with one geometry draws another one it receives later: a write completes the sub-layers the layer lacks. Not covered: a clustered layer, whose source indexes points alone.

Release v3.15.0 (2026-10-12) — a minor release, published with fifteen plugins and @geoleaf/field-renderer: a basemap's relief can follow the tilt of the map, a theme declares where it frames the map, the table says when a row is activated, and a new version of the application waits for the user before it takes over.

  • Added — a basemap's relief can follow the tilt of the map (terrain.activation: "pitch"): absent while the map is flat, put on when the camera stops tilted. The hillshade can be drawn as an overlay of any basemap (hillshade.overlay), and shown only while a relief is on (hillshade.onlyWithTerrain).
  • Added — a theme declares the view it frames the map on (themes[].view): an extent, or a centre and a zoom. The map starts on the view of the theme the profile starts on.
  • Added — the table says when a row is activated — a double-click, or Enter on the focused row — with geoleaf:table:rowActivated (@geoleaf-plugins/table 1.2.0). A double-click no longer changes the selection.
  • Added — a new version of the application waits for the user's gesture, and says so (geoleaf:sw:update-waiting, GeoLeaf.PWA.applyUpdate()); @geoleaf-plugins/offline-ui 1.7.0 shows it, acts on the captures set aside and exports the log.
  • Added — GeoLeaf.<Plugin> is typed for whoever installs the plugin, and GeoLeaf.Storage.CacheManager.cacheProfile() resolves with a typed result.
  • Fixed — map.initialMaxZoom caps the initial framing: it was documented as that ceiling and capped nothing. A profile that declares it on a small extent now starts at the zoom it asked for.
  • Fixed — the offline preparation judges each resource on the origins the profile declares, and scrolling a large table no longer keeps every row it scrolled past.
  • Breaking — three changes carry the mark in the changelog: the ambient type of GeoLeaf.Storage names every member and ends with no index signature (types only); six label keys that nothing drew leave the profile schemas; and the HTTPS rule of @geoleaf-plugins/connector 1.4.0 judges the destination of the credential, and covers the login URL.

Release v3.14.2 (2026-10-04) — a patch release, no new public member: an application mounted again gets its plugins back, a vector-tile layer of lines is drawn, and a configuration key that hardened nothing is gone.

  • Fixed — an application mounted again gives its plugins back, and takes them down when it is unmounted (@geoleaf-plugins/connector 1.3.5, editor 1.5.7, geocoding 1.1.3, measure 1.0.9, position-share 1.0.2, print 1.3.4, table 1.1.3). A plugin loaded on demand lost its toolbar button at the next mount and was never torn down; the editor and the measure tools kept the map of the previous application.
  • Fixed — a vector-tile layer declared polyline or multiline draws its lines. Both names are admitted by the layer schema; the vector-tile path read lists of its own that held neither, and such a layer was created with nothing on the map and no warning.
  • Removed — the security.httpsOnly configuration key, which nothing read. It was typed and documented as the way to refuse http: resources, and setting it changed nothing. The call-site option of validateUrl() is unchanged. To refuse http: for a whole deployment, use CSP upgrade-insecure-requests and HSTS.

Release v3.14.1 (2026-10-03) — a patch release, no new public member: a hatched layer and a gallery of photos each lose a way to drop something without a word.

  • Fixed — a hatched layer keeps its pattern when a basemap switch makes the engine rebuild the style instead of diffing it, which an incoming vector style declaring terrain or centerAltitude does. The layers were carried into the new style; the patterns they name were not, and a pattern-only fill drew nothing.
  • Fixed — a gallery's photos follow the same road as a single photo (@geoleaf/field-renderer 1.4.2, @geoleaf-plugins/editor 1.5.6). Taken off-network, each one is written back onto the feature by its URL once uploaded, in place of its token; with no upload endpoint, the gallery keeps the file instead of writing a blob: address that dies with the page.
  • Fixed — the preview of a photo waiting for its upload is displayed, in the form and in the lightbox, for a single photo and a gallery alike (@geoleaf/field-renderer 1.4.2).

Release v3.14.0 (2026-10-02) — a configuration copied from the FlatGeobuf plugin's README now passes profile validation.

  • New — the layer schema declares defaultVisible and cluster, the two root keys the FlatGeobuf plugin reads, for a layer declaring plugin: "flatgeobuf". They are refused on any other layer, where nothing reads them. The published schema and the profile types generated from it (@geoleaf/core/schemas) carry both.

Release v3.13.1 (2026-10-01) — a patch release, no new public member: the write cycle, the sign-in and the application's teardown each lose a way to go wrong without a word.

  • Fixed — an edit that fails is attempted four times, so the announced 8-minute delay is waited; a creation the server acknowledges without its row is looked up by its client identity instead of being marked synchronised with no server identity.
  • Fixed — @geoleaf-plugins/connector 1.3.4: one login window at a time, a sign-in bounded body included and validated as a renewal is, a refused renewal asked once, and the server's own 401 handed back with its headers.
  • Fixed — Core.destroy() called from a geoleaf:app:ready listener leaves no uncaught error; on a profile that declares no themes, a shared link keeps its layers and its filter, and no themes.json is requested.
  • Fixed — @geoleaf-plugins/print 1.3.3 credits the basemap on the printed page; @geoleaf-plugins/table 1.1.2 names a feature in a KML or GPX export as the table does; a form that is saving can no longer be closed under its write (@geoleaf/field-renderer 1.4.1, @geoleaf-plugins/editor 1.5.5).
  • Fixed — @geoleaf-plugins/realtime-layer 1.0.7 no longer installs a command-line toolchain; @geoleaf-plugins/offline-ui 1.6.3 and @geoleaf-plugins/measure 1.0.8 say what they used to pass in silence.
  • Fixed — documentation that contradicted the code: the "project with a profile" recipe goes through GeoLeaf.boot(), and the write-path descriptions say what the code does.

Release v3.13.0 (2026-09-29) — an application a host mounts and unmounts whole, and a profile that can be validated, and checked against its data, with what the package ships:

  • New — GeoLeaf.mount(el, options) returns a handle (ready, unmount(), on(), getMap()); unmount() takes the whole application down and the next mount() gives it back whole. boot({ maplibregl }) hands the MapLibre 6 engine over, and geoleaf:boot:failed says reason: "engine" when none is found. @geoleaf-plugins/realtime-layer 1.0.6 and @geoleaf-plugins/geocoding 1.1.2 unmount with the application.
  • New — the profile JSON Schemas ship with the package (@geoleaf/core/schemas/<name>.schema.json), with TypeScript types generated from them (@geoleaf/core/schemas).
  • New — a field a profile names and its data does not carry is said when the layer loads: one Log.warn per layer, naming the configuration key, the field and, when only the case differs, the name the data carries.
  • Fixed — the offline write cycle's members of GeoLeaf.Storage are typed, and what an application left behind once torn down (basemap, permalink, listeners, merged configuration) is gone.

Release v3.12.0 (2026-09-27) — what reads a layer follows its edits, and every package carries the licenses of the code it bundles:

  • New — geoleaf:layer:updated ({ layerId }): a layer's store changed through GeoLeaf.Layers, once per call. The open table (@geoleaf-plugins/table 1.1.1) and the active filter follow it: a feature created, edited, deleted or restored reaches them at once.
  • New — GeoLeaf.Layers.hideFeatures(layerId, ids | null) hides features without touching the store or the layer's filter. @geoleaf-plugins/editor 1.5.4 hides the feature it edits this way: editing under an active filter no longer lifts the filter.
  • New — THIRD_PARTY_LICENSES.txt in the core and in eight plugins: the license texts and notices of the third-party code each bundle carries, which minification had stripped.
  • Fixed — the editor deletes the selected feature, from the pill, the Delete key or a confirmation given from the keyboard; Enter presses the pill's buttons.
  • Fixed — a filter set on a clustered layer no longer draws its clusters as points and its points as bubbles; a vector-tile layer is created once at boot, without an error; the « pending » badge leaves an entity once it is pushed; and a device that edited features before 3.4.0 no longer draws them twice (local database version 7).

Release v3.11.2 (2026-09-27) — a feature created with the editor is in its layer at once, under its identity:

  • Fixed — a point placed or a shape drawn with @geoleaf-plugins/editor 1.5.3 enters its layer's store (GeoLeaf.Layers) under the identity its write returned: the server's id, or the client key (loc:…) for a write the offline outbox holds. It is found by getFeatureById and search, selected through its layer, and its later edits reach the outbox or the server under that identity. geoleaf:editor:feature-saved carries that key instead of "".
  • Fixed — a queued creation comes back once, under its identity, at the next load, instead of one anonymous copy per restore pass. The client key no longer leaves for the server as an id attribute, and an edit no longer sends back a photo token already delivered.
  • Removed — @geoleaf-plugins/offline-ui 1.6.2 drops a dictionary key the core already carries and reads first. Nothing changes for an integrator.

Release v3.11.1 (2026-09-27) — the filter panel says when the filter keeps no feature:

  • Fixed — a filter that matched nothing hid every feature without a word, and an empty map could not be told from a failure. The panel now reads « Aucune entité ne correspond aux filtres » (key ui.filter_panel.no_match, overridable through the profile's labels) between its controls and its buttons, and clears it as soon as an application keeps a feature. When nothing was judged (no layer loaded, or vector-tile layers only), it stays silent. A screen reader announces it (role="status").

Release v3.11.0 (2026-09-26) — the pre-departure check says whether the write session will let the captures made off-network reach the server:

  • New — GeoLeaf.Storage.preflight() carries session: valid with its expiry, expired, or absent, as the one holding the token tells it, or null when nobody does. An expired or absent session makes the verdict degraded; a valid one leaves the verdict as it is.
  • New — GeoLeaf.Sync.registerSessionReader(reader): the one holding the token registers a reader of the write session, which must not renew it and answers within two seconds. @geoleaf-plugins/connector 1.3.3 registers its own in auth.endpoint mode; a host that hands its own token over (getToken) registers here to say its expiry. @geoleaf-plugins/offline-ui 1.6.1 shows the session in the offline window.

Release v3.10.6 (2026-09-26) — a profile without a default theme boots with its legend, scale bar and coordinates, and starts on its first theme; the desktop side panel's tabs follow ui.language:

  • Fixed — when themes declared no defaultTheme, or when a profile had no themes, the app was revealed before the legend, scale, coordinates, filter and theme-selector capabilities had started listening for geoleaf:app:ready: none of them mounted, and nothing was logged. geoleaf:app:ready now fires once every module has started, and a capability that starts after it mounts at once.
  • Fixed — a profile whose themes declares no defaultTheme starts exactly as if it declared its first theme: only that theme's layers load at startup. Every other layer used to load in the background and stay visible over it. The legacy config.defautTheme key is honoured wherever a default theme is read.
  • Fixed — a modular profile without themes no longer keeps the loading veil up waiting for a themes.json it does not have.
  • Fixed — without a profile title, the desktop side panel's three tabs read the same translated labels as the mobile sheets, instead of French in every language.

Release v3.10.5 (2026-09-26) — the mobile toolbar keeps its filter button whatever fixed buttons are turned off:

  • Fixed — with geolocation, the theme selector or the layer manager turned off, the mobile toolbar lost its filter button while the filter panel was on. The button now sits immediately before the layers button, or after the last fixed button when there is none; a bar with every fixed button on keeps its layout.

Release v3.10.4 (2026-09-26) — the filter reaches line layers, and a vector-tile layer draws the labels it declares:

  • Fixed — a filter field without layers now filters line layers too; they used to stay drawn under any search. To keep a layer out of a field, list the layers that field targets in its layers. GeoLeaf.Layers.setVisibleSubset() now works on a line layer.
  • Fixed — the filter panel filters every loaded layer in one pass, so a layer of any declared kind is reached; geoleaf:filter:apply is emitted once per application.
  • Fixed — a vector-tile layer's labels name their source-layer and are drawn; a vector-tile layer created through GeoLeaf.Layers.create() arms the labels it declares.
  • Documented — a vector-tile layer is not filtered by the filter panel: it keeps no feature in memory.

Release v3.10.3 (2026-09-25) — destroying a set-aside field capture no longer leaves its entity on the map, and the documentation says what the code does:

  • Fixed — destroying a quarantined capture (GeoLeaf.Storage.discardQuarantined) returns its entity to the server's truth: the local record is removed when the server has nothing to give back, and marked for replacement by the next pull otherwise. It used to stay on the device, drawn on every load by layers that read the device first.
  • Fixed — documentation that contradicted the code: Storage.clearAll(), GeoLeaf.init()'s accepted keys, the 401/403 session rule, the requeueable quarantine motives, and ServerDeletionPolicy.
  • Fixed — two dead CSS rules of the feature-info side panel removed; nothing changes on screen. @geoleaf-plugins/geocoding 1.1.1 carries a corrected source comment.

Release v3.10.2 (2026-09-25) — a layers.json entry keeps the promises its schema makes: its label renames the layer, and the two keys that did nothing are gone:

  • Fixed — an entry's label now overrides the label of the layer config its configFile points to; the loader used to drop it.
  • Fixed — layers.schema.json no longer accepts order or defaultVisible on an entry. No code read either: a layer manager section lists its layers by descending zIndex, and visibility at load follows the theme. The schema is not part of the npm package; a running map is unaffected.

Release v3.10.1 (2026-09-25) — two fixes for a host that embeds the map: loading the bundle no longer touches the page, and a value set with Config.set before anything reads the configuration is kept:

  • Fixed — loading the bundle wrote <html lang>, before a host could set ui.syncDocumentLang: false: the layer manager's title and the branding's default text were translated when their module was imported. They are now translated when their control is built, so they follow the profile's ui.language and labels. The boot alone fixes the language and writes the attribute.
  • Fixed — GeoLeaf.Config.set dropped a value set before anything had read the configuration.
  • Documented — the active profile replaces each top-level section it carries, ui included: a host overrides a profile value, such as ui.language, from the beforeBoot hook.

Release v3.10.0 (2026-09-25) — a feature is found by its reference with no network, a form's option lists work off-network, the device says before going off-network whether it can leave, and layers placed outside the adapter survive a basemap switch:

  • New — GeoLeaf.Layers.search(query) finds features by reference in the layers declaring searchable: { fields }, from what the map holds; GeoLeaf.Layers.focus(layerId, id) frames one and selects it — the selected state is now painted. @geoleaf-plugins/geocoding 1.1.0 exposes it as the layers provider; given a list of providers, it skips the network ones off-network.
  • New — GeoLeaf.Storage.resolveOptions(url): a dropdown's fetchOptions list is kept on the device by the offline preparation and by any form opened online (@geoleaf/field-renderer 1.3.0, setOptionsResolver; wired by @geoleaf-plugins/editor).
  • New — GeoLeaf.Storage.preflight(): whether the browser keeps the data, each layer that declares something to pull and whether it is on the device, what the last preparation left out, and a verdict. @geoleaf-plugins/offline-ui 1.6.0 shows it in the offline window.
  • New — IMapAdapter.declareOwnedStyleIds: sources and layers a plugin places on the engine cross a basemap switch that replaces the style (@geoleaf-plugins/measure 1.0.6, @geoleaf-plugins/cog 1.0.4, @geoleaf-plugins/editor 1.5.1).
  • Deprecated — @geoleaf-plugins/geocoding: an unknown single provider value now warns, and will be refused by the next minor release.

Release v3.9.0 (2026-09-24) — a layer can stay out of the layer manager, a vector basemap applied at startup keeps the layers created while its style downloads, and a host can unmount the map during its boot.

Release v3.8.0 (2026-09-23) — the filter says which category a checked sub-category belongs to, and a selected shape in the editor moves when it is pressed near an edge.

Release v3.7.0 (2026-09-23) — a host sets the GeoJSON worker's URL, a layer declares its labels itself, a failed worker no longer fails a page's first layers, and the editor loads its drawing engine on the first tool use.

Release v3.6.1 (2026-09-22) — three fixes to the camera and the basemap, all three visible on a 3D basemap: it no longer waits for the user's first gesture to apply, it reaches the tilt it asks for, and re-framing the map keeps the angle instead of flattening it.

Release v3.6.0 (2026-09-20) — three seams a host application was missing: the map stops claiming the page's language, and the authority behind a layer's visibility — plus the style it wears — can now be read, not guessed:

  • New — ui.syncDocumentLang lets a host keep its own <html lang>. The attribute is page-wide, so a map embedded in a form used to rewrite the locale of everything around it. Defaults to true: a standalone application behaves exactly as before, and opting out suppresses only the write — the map still resolves its language normally.
  • New — Layers.getVisibilitySource(id) names which authority last set a layer's visibility ("user", "theme", "zoom", "system"). isUserOverridden only answers "not the user", which leaves a theme indistinguishable from a zoom threshold. The same value already travelled on the geoleaf:layer:toggle event; this is the pull route, for code that mounted after the change.
  • New — Layers.getStyle(id) returns which style a layer wears, as { id, label }. A normalised projection rather than the raw entry: the underlying field has two shapes depending on which writer spoke last, and only these two survive both.

Release v3.5.0 (2026-09-19) — a second download converges on its source instead of rewriting everything, and « Stop » stops the whole download, entities included:

  • New — a second pull rewrites only what changed (unchanged), and an entity deleted on the server leaves the device (removed). A layer can declare freshness and deletions — offline.source.delta — and then only asks for what changed.
  • New — the download window's zone bounds the entity pull, not only the tiles: its extent leaves with every pull as the OGC bbox.
  • New — the server contract page: what GeoLeaf asks of a server to pull, write and renew a session, in one place.
  • Fixed — a photo written back after the fact no longer erases the entity that owns it.
  • Fixed — « Stop » reaches the entity pull, and its confirmation is clickable.

Release v3.4.0 (2026-09-19) — the failure screen, the log export and the conflict store:

  • New — geoleaf:boot:failed names why a boot could not complete, and the app shell's loading veil becomes a failure screen with a Reload button and a diagnostic to copy. A watchdog, GeoLeaf.boot({ watchdogMs }), catches a boot that stops progressing.
  • New — GeoLeaf.Log.getEntries() and GeoLeaf.Log.exportDiagnostic(): the latest log entries, redacted when read.
  • New — a settled offline write conflict keeps the server version it overwrote, readable through Storage.listConflicts() and purgeable through Storage.clearConflicts().
  • Changed — the offline preparation no longer downloads tiles from an undeclared third-party origin. An origin whose terms allow it is declared in modules.offline.dataOrigins, with cacheable: true and prefetch: true.
  • Changed — the basemap credit is displayed.
  • Removed (breaking) — GeoLeaf.Security.CSRFToken and the csrf write authentication.
  • Fixed — GeoLeaf no longer unregisters service workers it did not register.

Releases v3.1.0 (2026-09-01), v3.2.0 (2026-09-08) and v3.3.0 (2026-09-09) are described in the changelog.


Release v3.0.0 2026-08-12 ​

Three new MIT plugins: @geoleaf-plugins/file-import, @geoleaf-plugins/flatgeobuf, @geoleaf-plugins/cog.

What changed in v3.0.0:

  • New — @geoleaf-plugins/file-import (MIT) — import GeoJSON, KML, GPX and CSV from the browser
  • New — @geoleaf-plugins/flatgeobuf (MIT) — streaming reader for FlatGeobuf files
  • New — @geoleaf-plugins/cog (MIT) — native WebGL rendering of Cloud Optimized GeoTIFF

Release v2.0.0 2026-03-22 ​

A major rendering engine migration: Leaflet → MapLibre GL JS v5. WebGL rendering, native GPU clustering, ESM-only.

Key changes:

  • Breaking — peer dependency leaflet → maplibre-gl, coordinates [lat,lng] → [lng,lat], scope geoleaf → @geoleaf/core
  • New — @geoleaf-plugins/connector v1.0.0 (MIT) — a universal fetch interceptor for authenticated geospatial sources
  • Removed — the UMD bundle; distribution is ESM-only
  • Tests — Jest → Vitest 3 migration, 8,317 tests, 77.97% branch coverage

Full release notes → · Changelog →

Released under the MIT License.