
maplibre-v6-migration
Upgrading a MapLibre GL JS app from v5 to v6 — the ESM-only build, the removed default export, CommonJS require() breakage, the styleimagemissing/setMissingStyleImageResolver change, the removal of the internal map.transform, the MapDataEvent → MapSourceDataEvent/MapStyleDataEvent split, and the bundler-only setWorkerUrl() requirement. Use when a v5 app breaks after upgrading to v6, or before pinning a v6 install.
- Installs
- 195
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/maplibre/maplibre-agent-skills --skill maplibre-v6-migration- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads maplibre-v6-migration automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
MapLibre GL JS v5 → v6 Migration
Use this only when the question, or the code in it, involves one of the seven changes below. For any other MapLibre question, such as sources, layers, styling or terrain, write the answer with v6 imports and add no section, table, list or note about v6 changes or migrating from v5.
MapLibre GL JS v6 (released 2026-07-22) removed several things v5 code relied on: the UMD/CSP browser bundles, the default export, CommonJS support, the internal map.transform, and the MapDataEvent type. Most models' training data predates v6. So for code that touches one of these seven, the natural-sounding answer is usually the v5 one, and it breaks on v6.
Primary reference: the MapLibre GL JS v5→v6 migration guide. This skill adds the gaps a model tends to fill with v5-era defaults. If the guide and this skill disagree, follow the guide and report it.
When to Use This Skill
- Upgrading an existing MapLibre GL JS v5 app to v6, or debugging a map that "used to work" after a dependency update
- Writing one of the seven patterns below: a CDN
<script>tag, an import statement, arequire()call, astyleimagemissinghandler, code that readsmap.transform, a typeddata/dataloading/dataaborthandler, or a bundler setup (Vite, webpack, esbuild, Rspack, Rollup) - Debugging errors like
ERR_PACKAGE_PATH_NOT_EXPORTED, a blank map after a CDN update, a sprite icon that never appears, a TypeScript error namingMapDataEvent, or a worker-loading error that blocks render
1. CDN script tag: ESM-only now
v6 removed the UMD bundle and the separate CSP build. There's no dist/maplibre-gl.js for a plain <script src="..."> tag anymore. Only the ESM build, dist/maplibre-gl.mjs, remains, and it needs type="module".
<!-- ❌ v5 — dist/maplibre-gl.js no longer exists in v6 -->
<script src="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
<!-- ✅ v6 — ESM build, type="module" required -->
<script type="module">
import * as maplibregl from 'https://unpkg.com/maplibre-gl@^6.0.0/dist/maplibre-gl.mjs';
const map = new maplibregl.Map({ container: 'map', style: '...' });
</script>
Always pin an explicit major (@^6.0.0, or a specific version). Never use @latest or a bare unversioned specifier — an unpinned CDN URL breaks the page silently the moment the next major version publishes. This already happened at the v5→v6 boundary.
2. No default export — named or namespace import only
v5's import maplibregl from 'maplibre-gl' (default import) no longer works in v6.
// ❌ v5 — default export removed
import maplibregl from 'maplibre-gl';
// ✅ v6 — namespace import
import * as maplibregl from 'maplibre-gl';
// ✅ v6 — or import only what you use
import { Map, NavigationControl } from 'maplibre-gl';
This applies whether the code is new or converted from a Mapbox GL JS snippet (import mapboxgl from 'mapbox-gl'). Don't carry the default-import shape over either way.
3. No CommonJS — require('maplibre-gl') throws
maplibre-gl's package.json exports field has only an "import" condition in v6, not "require". So require('maplibre-gl') throws ERR_PACKAGE_PATH_NOT_EXPORTED in plain Node. That's expected behavior, not a bug in the caller's setup.
// ❌ throws ERR_PACKAGE_PATH_NOT_EXPORTED in v6
const maplibregl = require('maplibre-gl');
// ✅ convert the file to ESM (named/namespace import — see item 2)
import { Map } from 'maplibre-gl';
// ✅ or, if the caller must stay CommonJS, load it dynamically
const { Map } = await import('maplibre-gl');
This section is only about a bare Node require() call. If a bundler is involved (webpack, Vite, etc.) and require() still fails, that's a separate ESM-interop config issue.
4. styleimagemissing no longer resolves the request — use setMissingStyleImageResolver
In v5, listening for styleimagemissing and calling map.addImage() synchronously from the handler would supply the missing icon. In v6, styleimagemissing is notify-only. Calling addImage from the handler no longer resolves the pending request.
// ❌ v5 pattern — no longer resolves the request in v6
map.on('styleimagemissing', (e) => {
map.addImage(e.id, generateIcon(e.id));
});
// ✅ v6 — register a resolver that adds the image itself
map.setMissingStyleImageResolver(async (id) => {
map.addImage(id, await generateIcon(id));
});
The resolver has to call map.addImage(). MapLibre ignores its return value, so returning the image does nothing.
Use this whenever a style references icon-image names that aren't in the sprite sheet and need to be generated or fetched at runtime.
5. map.transform is gone — use the public Camera API
v6 refactored Map to compose a Camera instead of extending it. Map now extends Evented directly and forwards the camera API. The internal map.transform property was removed as part of that change.
// ❌ v5 — reaching into the internal transform
const { zoom, bearing, pitch } = map.transform;
const center = map.transform.center;
// ✅ v6 — public accessors
const zoom = map.getZoom();
const bearing = map.getBearing();
const pitch = map.getPitch();
const center = map.getCenter();
There's no general public replacement for the raw projection/view matrix. It was never exposed on Map itself, in v5 or v6 — it only ever lived on map.transform. The one place a matrix is still available is inside a custom layer's render() callback, via the callback's arguments (CustomRenderMethodInput.getProjectionData() / defaultProjectionData; see the custom layers API). Outside a custom layer, use the public getters above. For anything else the public API doesn't expose, open an issue or PR instead of reintroducing a private accessor.
6. MapDataEvent removed — use MapSourceDataEvent / MapStyleDataEvent
v6 made every fired event a real class, instantiated per event. The old catch-all MapDataEvent type is gone. data, dataloading, and dataabort are now typed as MapSourceDataEvent | MapStyleDataEvent. A source data event carries its full source info (sourceId, tile, sourceDataType, ...) directly on its own type, instead of a generic shared shape.
// ❌ v5 — MapDataEvent no longer exists in v6
import type { MapDataEvent } from 'maplibre-gl';
map.on('data', (e: MapDataEvent) => {
/* ... */
});
// ✅ v6 — narrow on the union MapLibre now exports
import type { MapSourceDataEvent, MapStyleDataEvent } from 'maplibre-gl';
map.on('data', (e: MapSourceDataEvent | MapStyleDataEvent) => {
if ('sourceId' in e) {
// MapSourceDataEvent — e.sourceId, e.sourceDataType, e.tile
} else {
// MapStyleDataEvent
}
});
The same fix applies anywhere a type import or annotation names MapDataEvent, including dataloading and dataabort handlers.
7. Bundled builds still need setWorkerUrl() — CDN ESM does not
v6 changed how the source-processing worker is located, but only for bundled apps. A plain CDN <script type="module"> (item 1) auto-detects the worker URL from import.meta.url and needs no extra setup. Inside a bundler (Vite, webpack, esbuild, Rspack, Rsbuild, Rollup), import.meta.url doesn't reliably resolve to the worker file within the bundler's own module graph. Each of these setups needs one explicit setWorkerUrl() call, made once before creating the Map.
// ❌ v6 with a bundler, no setWorkerUrl() call — worker fails to load, map never renders
import { Map } from 'maplibre-gl';
const map = new Map({
/* ... */
});
// ✅ Vite — the `?worker&url` query
import { Map, setWorkerUrl } from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
setWorkerUrl(workerUrl);
const map = new Map({
/* ... */
});
// ✅ webpack 5+, Rspack, Rsbuild, and Next.js 15+ (Turbopack or webpack) — call setWorkerUrl() the same way, then create the Map
import { Map, setWorkerUrl } from 'maplibre-gl';
setWorkerUrl(new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url).toString());
In Vite, use ?worker&url, as the install guide does. From 6.0.0 to 6.12.x, plain ?url leaves behind the maplibre-gl-shared.mjs the worker imports, so no vector tiles load in production; 6.13.0 made the worker self-contained. esbuild and Rollup copy the worker file and pass its URL to the same one-time call. See the install guide for current per-bundler snippets; they change with tooling versions faster than the rest of this skill.
Don't carry this into item 1's CDN case. A plain <script type="module"> import from a CDN URL never needs setWorkerUrl().
Reference
- MapLibre GL JS v5→v6 migration guide and its CHANGELOG (v6.0.0 section)
- CustomLayerInterface API docs and install guide (per-bundler
setWorkerUrl()snippets)
This skill is a snapshot. Where a primary source contradicts it — the References above, MapLibre's current documentation, or what MapLibre does when you run it — that source wins. Follow it, then report the disagreement, citing the source and your MapLibre version: editing your installed copy helps no one else and is overwritten on the next update.
FAQ
What does the maplibre-v6-migration skill do?
Upgrading a MapLibre GL JS app from v5 to v6 — the ESM-only build, the removed default export, CommonJS require() breakage, the styleimagemissing/setMissingStyleImageResolver change, the removal of the internal map.transform, the MapDataEvent → MapSourceDataEvent/MapStyleDataEvent split, and the bundler-only setWorkerUrl() requirement. Use when a v5 app breaks after upgrading to v6, or before pinning a v6 install.
How do I install maplibre-v6-migration?
Run npx skills add https://github.com/maplibre/maplibre-agent-skills --skill maplibre-v6-migration in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/maplibre-v6-migration/ (personal) or .claude/skills/maplibre-v6-migration/ (project).
Which AI agents can use maplibre-v6-migration?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is maplibre-v6-migration safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains maplibre-v6-migration?
It is published by maplibre in maplibre/maplibre-agent-skills. It has 195 recorded installs.
More from maplibre/maplibre-agent-skills
maplibre-tile-sources
maplibre/maplibre-agent-skills
Choosing what form your data needs for MapLibre GL JS — GeoJSON read straight from a file versus vector or raster tiles, the feature-count and file-size thresholds where each stops working, and how a basemap and your own data combine as separate sources. Use when deciding which source type a dataset needs, not when hosting or configuring one.
maplibre-pmtiles-patterns
maplibre/maplibre-agent-skills
Serverless vector and raster tiles with PMTiles for MapLibre GL JS — single-file format, HTTP range requests, hosting on S3/R2/GitHub Pages, generating with Planetiler or tippecanoe, and the pmtiles protocol. Use when you need no tile server or want to host tiles from static storage.
maplibre-mapbox-migration
maplibre/maplibre-agent-skills
Migrating from Mapbox GL JS to MapLibre GL JS — package and import changes, removing the access token, choosing tile sources, plugin equivalents, and what you gain or give up. Use when moving an existing Mapbox map to MapLibre.
maplibre-cartography
maplibre/maplibre-agent-skills
Open the skill to read its SKILL.md instructions.
maplibre-source-wiring
maplibre/maplibre-agent-skills
Getting a MapLibre GL JS source to actually render — TileJSON url versus hand-wired tiles templates, matching source-layer names to the tile schema, layer order and inserting below labels, promoteId and the feature ids that feature state needs, and the CORS and glyph failures behind a blank map. Use when a source is configured but nothing is drawing, or when setFeatureState does nothing.
maplibre-fonts-glyphs
maplibre/maplibre-agent-skills
Open the skill to read its SKILL.md instructions.
Popular code quality & workflow skills
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
improve-codebase-architecture
mattpocock/skills
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
handoff
mattpocock/skills
Compact the current conversation into a handoff document for another agent to pick up.
domain-modeling
mattpocock/skills
Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.
codebase-design
mattpocock/skills
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
diagnosing-bugs
mattpocock/skills
Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.