# v5 to v6 migration guide

MapLibre GL JS v6 ships as ES modules only. The UMD bundle, the separate CSP build, and the CommonJS (`require('maplibre-gl')`) entry from v5 are all gone. The bundle file is now `maplibre-gl.mjs` (and `maplibre-gl-worker.mjs`). If your build tooling or test runner still uses `require()` (plain Node scripts, test runners that don't transform ESM, server-side code importing the package without a bundler), this surfaces as `ERR_PACKAGE_PATH_NOT_EXPORTED`.

## Imports

If you import maplibre-gl from npm with **named imports** (`import {Map} from 'maplibre-gl'`), your imports keep working: v6 resolves to the ESM bundle automatically.

If you used the **default import** (`import maplibregl from 'maplibre-gl'`), switch to either named imports or a namespace import:

```ts
// before
import maplibregl from 'maplibre-gl';

// after
import * as maplibregl from 'maplibre-gl';
// or pull in just what you need
import {Map, setWorkerUrl} from 'maplibre-gl';
```

## `<script>` tag

If you load maplibre-gl via `<script src>`, switch to a module script:

```html
<!-- before -->
<script src="https://unpkg.com/maplibre-gl@^5/dist/maplibre-gl.js"></script>

<!-- after -->
<script type="module">
    import * as maplibregl from 'https://unpkg.com/maplibre-gl@^6.0.0/dist/maplibre-gl.mjs';
</script>
```

Pin an explicit major version (e.g. ^6.0.0) rather than `@latest` or an unversioned specifier. Starting with v6, a page pinned to `@latest` goes to a blank gray screen with a 404 in the console.

## `setWorkerUrl()` is bundler-only

For direct browser ESM (loading from a CDN like unpkg via a `<script type="module">` tag), the worker URL is auto-detected from `import.meta.url` and laundered through a same-origin Blob URL when needed, so no [`setWorkerUrl()`](<https://maplibre.org/maplibre-gl-js/docs/API/functions/setWorkerUrl/index.md>) call is required.

For bundlers (Vite, webpack, esbuild, rspack, Rollup), `import.meta.url` doesn't reliably resolve to the worker file inside the bundler's module graph, so each consumer still needs a one-time `setWorkerUrl()` call. See [Installation](<https://maplibre.org/maplibre-gl-js/docs/#installation>) for per-bundler snippets.

## CSP directives

The dedicated CSP bundle from v5 is no longer needed.

If you load MapLibre from a CDN cross-origin to your page (e.g. unpkg), the worker is constructed from a same-origin Blob URL, so your CSP needs to allow `blob:` in `worker-src`:

```text
worker-src 'self' blob: ;
img-src data: blob: 'self' ;
```

If you self-host the worker file (any bundler setup), the worker URL is same-origin and `blob:` is not required:

```text
worker-src 'self' ;
img-src data: blob: 'self' ;
```

## zoomLevelsToOverscale

In version 5 there was an experimental parameter added to allow slicing vector tiles instead of overscaling them. We tested it, and it looks like it fixes a lot of issues in labeling etc. It changes rendering and the results of queryRenderedFeatures. If you would like to revert to the previous behavior you can set `zoomLevelsToOverscale: undefined` when initializing the map.

## Nested GeoJSON properties

Nested objects and arrays in GeoJSON feature properties are now preserved: features returned from events and `queryRenderedFeatures` contain them as real objects instead of JSON strings. If you called `JSON.parse` on such properties, remove it — it now throws `SyntaxError: "[object Object]" is not valid JSON`.

```diff
-const info = JSON.parse(e.features[0].properties.info);
+const info = e.features[0].properties.info;
```

## pragma mapbox

In case you were using `#pragma mapbox` in your shared code please replace it with `#pragma maplibre`.

```diff
-#pragma mapbox
+#pragma maplibre
```

## Events

All events are now classes, it is advised not to use `instanceof` but instead check the `type` field. Since the change was from types to classes this shouldn't be a problem in most code bases.

### styleimagemissing

In v6, `styleimagemissing` listeners can no longer resolve the current image request by calling `Map#addImage`. To migrate a listener that supplies missing images, replace it with [`Map#setMissingStyleImageResolver`](<https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#setmissingstyleimageresolver>):

```diff
-map.on('styleimagemissing', ({id}) => {
+map.setMissingStyleImageResolver((id) => {
     map.addImage(id, generateImage(id));
 });
```

The resolver can be synchronous or asynchronous. For asynchronous loading, call `Map#addImage` before the resolver's promise settles. The `styleimagemissing` event can still be used to observe images that remain unresolved.

## WebGL2 is now required

WebGL1 support has been removed; WebGL2 is now required. A browser or device that does not support WebGL2 will fail to render a map under v6. When WebGL2 is unavailable, the `Map` constructor throws a `GPUInitializationError` (check with `instanceof GPUInitializationError`, exported from `maplibre-gl`) instead of returning a map.

## `map.transform` was removed

The internal `map.transform` property has been removed; `Map` now composes a `Camera` rather than extending it. Use `Map`'s public API instead of reaching into `transform`. If you relied on something `transform` exposed that isn't covered by the public API, please open an issue or PR.
