maplibre_flutter
A Flutter plugin that draws MapLibre vector maps with one C++ engine, MapLibre Native, on Android, iOS, macOS, Windows, Linux and the web.

Juho Torkkeli

Overview
maplibre_flutter draws MapLibre vector maps with the same engine, the MapLibre Native C++ core (mbgl-core), on all six Flutter platforms. The engine renders off-screen and Flutter composites the frames through its texture pipeline. On the web, the engine is compiled to WebAssembly and draws into a canvas. There’s no MapLibre GL JS WebView on desktop, and the public Dart API is the same everywhere.
I started it in June 2026 because I needed vector maps in a Flutter desktop app and couldn’t find a solid way to do it. The goal is the best MapLibre package for Flutter, with one unified API that’s easy to use. The blog post Why I wrote my own MapLibre plugin for Flutter tells the story.
How it works
It’s a federated plugin: the package apps depend on, a platform interface, a shared core package and one package per platform.
- The core is a C ABI shim over
mbgl-corewith Dart bindings generated by ffigen. A Dart build hook (native_toolchain_cmake) compiles the engine from a pinned git submodule and applies the plugin’s own engine patches. - Rendering happens off-screen on a dedicated render thread. Each native platform hands finished frames to a Flutter
Texture, and the web draws into a canvas:
| Platform | Engine backend | How frames reach Flutter |
|---|---|---|
| Android | OpenGL ES | Flutter SurfaceProducer texture |
| iOS | Metal | IOSurface, zero-copy |
| macOS | Metal | IOSurface, zero-copy |
| Windows | Vulkan | Direct3D 11 shared handle, CPU fallback |
| Linux | OpenGL ES / EGL | dmabuf, CPU fallback |
| Web | WebAssembly / WebGL2 | a <canvas> in an HtmlElementView |
- Zero-copy presentation is on by default wherever it’s supported, and each platform falls back to a CPU copy automatically when the GPU path isn’t available.
- Gestures and camera are implemented once in Dart on top of the engine, so a feature wired on one native platform is wired on all five.
- Opt-in renderers are separate packages: the MapLibre Android SDK through jnigen, the MapLibre Apple SDK through swiftgen, and MapLibre GL JS on the web. Adding one overrides the default for that platform. Without it, its native code never ships.
Features today
On every platform, including the web:
- camera control: read the position, move, jump and fly to
- style switching as a declarative widget property
- pan and zoom gestures
Written once for all five native platforms (Android, iOS, macOS, Windows and Linux) and verified on macOS first:
- Widget markers: real Flutter widgets glued to coordinates, tappable and draggable. They’re projected against the frame actually on screen, so they don’t swim while the map moves. About 500 rich markers run smoothly in a release build on macOS.
- Engine layers: sources, layers and images in the style itself, drawn by the engine, with clustering built in. They scale past 100,000 points, and a Flutter widget can be rasterized into a style icon.
- Typed style API, generated from the MapLibre Style Spec: all 10 layer types, 6 source types, 33 enums and 84 expression operators.
- Feature queries with
queryRenderedFeatures, style transitions and camera change notifications.
3D .glb models, drawn inside the engine and depth-tested against buildings, work on macOS (Metal) only for now.
Not bound yet on the main branch: rotate and pitch gestures, offline storage, terrain and hillshade, the location component and built-in controls. The engine supports all of them. It’s binding work.
macOS is the reference platform where every feature is verified first. I’ve run the map on real devices on every Flutter platform, a physical Android phone included. The extended API above shares one binding across the five native platforms, and the repo’s feature matrix marks what has actually been run where. The tests cover the platform interface, the widget, the C shim through native test harnesses, and integration tests that run a real map.
An engine patch
Center-anchored text in MapLibre is positioned from a hardcoded baseline instead of the font’s metrics, so labels placed along lines sit off center. It’s a long-known limitation, reported against Mapbox GL JS in 2013. The plugin carries a patch that centers text on the glyphs’ actual ink: across 24 orientations, labels went from 4.03 px off center to 0.25 px. It’s not reported upstream yet. The write-up has the measurements and the root cause.
In progress
Three large branches aren’t merged yet:
- Accessibility: the map as a labeled screen-reader region, accessible markers, keyboard control with a visible focus ring, reduced motion, high contrast, and a feature list as an accessible alternative to the map.
- API parity with the native SDKs and GL JS, driven by a 703-row binding spec: camera verbs like
easeToandfitBounds, feature state, offline region downloads, custom HTTP headers, a snapshotter and a location puck. - Cross-platform parity: 3D models and rotate and tilt gestures on every native platform, and one conformance test suite across all five native platforms.
Status
Pre-release. The git tags 0.0.2 (June 17, 2026) and 0.0.3 (July 31, 2026) exist, but the package isn’t on pub.dev yet. The engine is a multi-gigabyte submodule that’s left out of the published archive, so using the plugin today means building from source. Next up is GitHub CI that builds the native cores, so the package works as a plain pub.dev dependency without compiling the engine. Then it goes to pub.dev.
It’s my personal project, published under Mankeli Solutions because publishing is easier that way. BSD-3-Clause licensed.
Tech
- Flutter, Dart
- C++ (MapLibre Native
mbgl-core), Objective-C++, Swift, Kotlin - Metal, Vulkan, Direct3D 11, OpenGL ES / EGL
- Emscripten, WebAssembly, WebGL2
- ffigen, jnigen, swiftgen
- Dart build hooks,
native_toolchain_cmake, CMake - Melos, pub workspaces
- Claude Code


