Library / SDK
dimfeld/svelte-maplibre avatar
dimfeld/svelte-maplibre

svelte-maplibre wraps MapLibre for Svelte, worker setup included

Svelte bindings for the MapLibre mapping library

522 stars64 forksSvelteMIT

At a glance

What is it?
svelte-maplibre is a set of Svelte bindings for the MapLibre GL JS mapping library, with a MapLibre component, per-item exports for filters, sources and expressions, and a context module for sharing the map instance. Its own README calls the library functional but still experimental and short of documentation, and that caveat is the main thing to weigh before adopting it.
Who is it for?
svelte-maplibre fits a Svelte team that has already chosen MapLibre GL JS and wants the map wired into components with typed props rather than imperative calls, and it does not fit a team that needs documented API guarantees today, since the author states the library still needs proper documentation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly Svelte, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A wrapper over MapLibre GL JS, and the author's own caveat

svelte-maplibre is a Svelte wrapper for the MapLibre mapping library, the open source map renderer, and it is published on npm as svelte-maplibre. The homepage of the package points at a documentation site hosted at svelte-maplibre.imfeld.dev, and the README points at a demo site at svelte-maplibre.vercel.app.

The README opens with a paragraph that any adopter should read before anything else. The author describes the library as functional, but says he is still experimenting with extra features to make advanced functionality easier to use, and that it needs proper documentation. In the meantime, the demo site includes the code for all the examples and is described as a good place to start.

That is a candid statement, and it is a real constraint on adoption. A wrapper library whose documentation is a demo site means the examples are the specification, and the examples are tied to the state of the code on a given day. If you need a stable, documented API with a changelog you can read, you are looking at something else. If you are comfortable reading a component's source and adapting it, the situation is different, and the demo site is genuinely a strong starting point for a mapping project.

The intended user is a Svelte developer who has decided on MapLibre and wants to use it from components. The value is in the glue code, not in the map.

The worker URL is the part that breaks first

Almost every complaint about using a mapping library in a modern build setup comes down to the same thing: MapLibre runs its rendering work in a web worker, and the bundler has to know where that worker file lives. The README handles this explicitly for Vite.

js
import * as maplibregl from 'maplibre-gl';
import maplibreWorkerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';

maplibregl.setWorkerUrl(maplibreWorkerUrl);

The query string on the import is the part to understand. The ?worker&url suffix tells Vite to emit the worker as an asset and hand back its URL, and setWorkerUrl then points MapLibre at that URL. The README is precise about the ordering, saying to call setWorkerUrl in the page component that creates the map, or in anything else that loads before the map.

That ordering requirement is the sort of detail that turns into a confusing bug report, because a map created before the worker URL is set will fail in a way that does not obviously point at the worker. If you are not using Vite, the README sends you to the MapLibre installation instructions for the worker configuration instead of guessing.

The Vite variant puts the same call in a module script block, which is the Svelte 5 way of running code once at module scope rather than per instance.

svelte
<script module>
  import * as maplibregl from 'maplibre-gl';
  import maplibreWorkerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
  maplibregl.setWorkerUrl(maplibreWorkerUrl);
</script>

The component takes the options you already know

The component API is the MapLibre API with the awkward parts removed. The example in the README shows a map with a center, a zoom level, a class, the standard controls and a style URL.

svelte
<MapLibre
  center={[50, 20]}
  zoom={7}
  class="map"
  standardControls
  style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
/>

The center is an array of two numbers, zoom is a number, and the rest are named flags and strings that map onto the MapLibre constructor options. There is no separate options object to learn, which is the main ergonomic win over calling the MapLibre constructor yourself inside an onMount.

Two details in the example are worth copying. The style is a URL to a style JSON served by a tile provider, so the map needs a style source rather than a local style, and the component takes a class attribute. That class has to be styled from outside the component's scope, which is why the example styles it with a global selector.

svelte
<style>
  :global(.map) {
    height: 500px;
  }
</style>

That is Svelte's scoped styling working as intended, and it is a small example of the kind of thing you discover by reading the component's source rather than a documentation page. A map needs a height, the height comes from CSS, and the CSS has to be applied globally to reach the container the component renders.

The package exports show what the API is made of

The package.json export map is the most compact description of the library's surface, and it is worth reading before you install anything.

The root export gives the main entry point with types, a svelte condition and a default condition, so the package is consumable both by Svelte-aware tooling and by plain bundlers. Then there is a wildcard for individual .svelte components with their own type declarations, a wildcard for .css files, and a set of named subpath exports: context.svelte.js, expressions.js, filters.js and source.js, each with a types entry and a default entry.

That tells you the library is not one component. There are separate modules for expressions, for filters and for sources, which are the parts of MapLibre that are otherwise the most verbose to write, since they are declarative JSON-like structures. The context module is the Svelte context mechanism, which is how a map instance is shared with child components such as markers and popups without prop drilling.

json
    "./context.svelte.js": {
      "types": "./dist/context.svelte.d.ts",
      "default": "./dist/context.svelte.js"
    },

The naming of that module is itself informative. The .svelte.js suffix is the Svelte 5 convention for a module that contains runes, so this is a reactive module built on the current Svelte primitives rather than a plain helper file written for the older component model.

The published files list is just dist, so what you install is the built output, and the build pipeline that produces it is described in the scripts.

SvelteKit, changesets and Playwright are the maintenance machinery

The repository is a SvelteKit project that also publishes a library, and the scripts show the split between the two.

json
    "build": "vite build && pnpm run package",
    "package": "svelte-kit sync && svelte-package && publint",

The build runs the site build and then the packaging step, which syncs the SvelteKit types, runs svelte-package to emit the library into dist, and finishes with publint to check that the published package is well formed. The exports map and the files list are what publint is checking against.

Testing is two layers. Playwright runs the end to end tests, with a playwright.config.ts at the root, and vitest is the unit test runner, with a separate test:unit script. Checking types is done with svelte-check against the project's tsconfig, and there is a watch mode for it. Linting and formatting are prettier and eslint, with separate check and write scripts, and husky with a .lintstagedrc set up as the git hook that runs them.

Releases go through changesets. The .changeset/ directory is at the root, there is a changeset script, and a generate-changelog script that reads a GitHub token and runs changeset version. RELEASING.md at the root documents the process, and CHANGELOG.md is the output, which the README links to as the first thing to read when you want to know what changed.

There is also an .env.example with a single variable in it, commented as being needed for the 3D buildings page to work, and the README explains that the only environment variable currently required is a MapTiler API key, and that you can leave it blank to use the other examples. That is a good sign about the default examples, and a reminder that the 3D example needs a paid tier key of its own.

Version 2 exists because MapLibre moved to v6

The release history is short and its titles are informative. v1.3.0 came out on 2026-03-31. Then v2.0.0 on 2026-08-05 with the title Support maplibre v6, and v2.0.1 on 2026-09-25.

So this project's major version is tied to the major version of the map engine underneath it, which is a defensible versioning policy and also a coupling you have to plan for. A breaking change in MapLibre means a breaking change here, and the two are not released in lockstep, so there will be a window in which the wrapper supports the new engine and you have not upgraded yet, or the other way round.

The last push to the repository was on 2026-10-03, and the repository is not archived, so the wrapper is tracking its dependency rather than sitting still. The default branch is master.

The licence is MIT, which is the permissive end and appropriate for a binding library that other packages will depend on.

There is a components.json in the root, which is the configuration file for the shadcn style component distribution approach, and that is a hint about the intended audience, since it suggests the library is meant to be consumed the way component libraries are now commonly consumed, with the code in your project rather than behind a black box.

When you would write the glue code yourself

The honest limitation is the one the author names. There is no proper documentation, the feature set is still moving, and the examples live in a demo site that can change under you. If your map is the centre of the product, that is a risk to accept deliberately rather than a detail.

The second limitation is that a wrapper cannot hide MapLibre. Tile providers, style URLs, the sources and layers you compose, the coordinate system, the interaction handlers: all of that is MapLibre, and the wrapper only saves you the constructor call and the imperative calls. Once a map has real behaviour, you are reading MapLibre documentation regardless of which Svelte component you imported.

The alternative is to skip the wrapper and drive maplibre-gl-js from your own Svelte component, which is entirely feasible given that the wrapper itself is a thin layer, and the difference in approach is ownership. With the wrapper you get a component with props, a context module and helpers for filters and expressions, and you inherit its API surface and its release cadence. Without it you write the onMount and onDestroy lifecycle, the reactive prop to option plumbing and the cleanup yourself, and you keep full control of the version you pin and the code you read.

A middle path is what the README actually suggests: start from the demo site's example code, since it is current and complete, and lift what you need from there. For a project that needs one or two maps with a few markers, that is often less work than either adopting a moving library or writing a general wrapper, and it is a reasonable answer to the wrong-tool question for most teams.

Editorial conclusion

svelte-maplibre fits a Svelte team that has already chosen MapLibre GL JS and wants the map wired into components with typed props rather than imperative calls, and it does not fit a team that needs documented API guarantees today, since the author states the library still needs proper documentation. Before you commit, open the demo site and read the source of the examples, because that is where the current recommended usage lives, and confirm the MapLibre major version your project pins matches the 2.x line, since v2.0.0 was released specifically to support MapLibre v6. The last push was on 2026-10-03 and the licence is MIT.

Frequently asked questions

How do I install svelte-maplibre?

The installation is a single npm install svelte-maplibre. The package is published from a SvelteKit project with svelte-package and publint in the build pipeline, and the root export provides type declarations alongside the Svelte and default conditions.

Why does my MapLibre map fail to load in a Svelte project?

The MapLibre worker has to be registered before the first map is created. The README shows importing the worker URL with the ?worker&url suffix and calling maplibregl.setWorkerUrl in the page component that creates the map, or in anything that loads before it, and points to the MapLibre installation instructions for other bundlers.

Does svelte-maplibre support MapLibre v6?

Yes, that is what the v2.0.0 release was for, published on 2026-08-05 with the title Support maplibre v6, and followed by v2.0.1 on 2026-09-25. The wrapper's major version therefore follows the map engine's major version.

Where are the docs for svelte-maplibre?

The README says the library needs proper documentation and points at the demo site, which includes the code for all the examples, as the place to start. The package homepage is svelte-maplibre.imfeld.dev, the demo site is svelte-maplibre.vercel.app, and there is a CHANGELOG.md linked from the README.

Do I need an API key to try svelte-maplibre?

Not for most of the examples. The README says the only environment variable is a MapTiler API key, required for the 3D buildings example, and that you can set it to a blank value to use the other examples. The .env.example file in the repository holds that single variable.

Official sources

  1. dimfeld/svelte-maplibre on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dimfeld-svelte-maplibre.svg)](https://hysenlabs.com/projects/dimfeld-svelte-maplibre)

Was this useful?