Flutter MapLibre GL lets you embed interactive, vector-tile based, fully styleable maps directly inside your Flutter app across mobile & web.
This project is a fork of flutter-mapbox-gl, replacing its usage of Mapbox GL libraries with the open source MapLibre GL libraries.
Click to expand
Capability | Description |
---|---|
Vector tile rendering | High quality, styleable vector maps via MapLibre engines |
Dynamic styling | Swap or mutate styles at runtime, apply expressions |
Camera control | Smooth programmatic & gesture driven map camera updates |
User location | Native location indicator & tracking (permissions required) |
Annotations | Symbols, circles, lines, fills, fill extrusions, heatmaps |
Web support | Backed by maplibre-gl-js for web targets |
Offline friendly | Patterns for mbtiles / cached assets (see advanced topics) |
Extensible | Separate platform interface & web implementation packages |
MapLibre is a vendor-neutral, open source set of mapping libraries born from the community. Using MapLibre helps you:
- Avoid vendor lock-in & proprietary billing tie-ins
- Host your own tiles or mix commercial/open sources
- Keep transparent, auditable code in production
- Align with OSS licensing and long-term sustainability
- One of the fastest map SDKs available. Capable of drawing a large number of vector objects.
If you previously used flutter-mapbox-gl
, you can migrate with minimal changes (see migration guide).
Underlying engines:
- Android / iOS — maplibre-native
- Web — maplibre-gl-js
Note: Only a subset of the native SDK APIs are currently exposed. PRs to extend surface area are welcome.
Feature | Android | iOS | Web |
---|---|---|---|
Style | ✅ | ✅ | ✅ |
Camera | ✅ | ✅ | ✅ |
Gesture | ✅ | ✅ | ✅ |
User Location | ✅ | ✅ | ✅ |
Symbol | ✅ | ✅ | ✅ |
Circle | ✅ | ✅ | ✅ |
Line | ✅ | ✅ | ✅ |
Fill | ✅ | ✅ | ✅ |
Fill Extrusion | ✅ | ✅ | ✅ |
Heatmap Layer | ✅ | ✅ | ✅ |
Add the dependency:
dependencies:
maplibre_gl: ^LATEST_VERSION
(See the current version badge above or on pub.dev).
Then run:
flutter pub get
Add permission usage description for location (if using location features) to ios/Runner/Info.plist
:
<key>NSLocationWhenInUseUsageDescription</key>
<string>[Explain why the app needs the user's location]</string>
Minimum supported Kotlin version: 2.1.0
. In android/settings.gradle
:
plugins {
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
Add location permissions if required in android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Request permissions at runtime yourself (e.g. via the location
plugin). The plugin does not prompt automatically.
Include the following JavaScript and CSS files in the <head>
of your web/index.html
file:
<script src="https://unpkg.com/maplibre-gl@^4.3/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@^4.3/dist/maplibre-gl.css" rel="stylesheet" />
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class SimpleMapPage extends StatefulWidget {
const SimpleMapPage({super.key});
@override
State<SimpleMapPage> createState() => _SimpleMapPageState();
}
class _SimpleMapPageState extends State<SimpleMapPage> {
final _controllerCompleter = Completer<MapLibreMapController>();
bool _styleLoaded = false;
static const _initial = CameraPosition(target: LatLng(0, 0), zoom: 2);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MapLibre Quick Start')),
floatingActionButton: _styleLoaded
? FloatingActionButton.small(
onPressed: _goHome,
child: const Icon(Icons.explore),
)
: null,
body: MapLibreMap(
initialCameraPosition: _initial,
onMapCreated: (c) => _controllerCompleter.complete(c),
onStyleLoadedCallback: () => setState(() => _styleLoaded = true),
),
);
}
Future<void> _goHome() async {
final c = await _controllerCompleter.future;
await c.animateCamera(CameraUpdate.newCameraPosition(_initial));
}
}
Check the example project for richer demos (markers, layers, offline patterns, PMTiles, etc.).
await controller.animateCamera(
CameraUpdate.newLatLngBounds(bounds, left: 24, top: 24, right: 24, bottom: 24),
);
Add symbols (markers):
await controller.addSymbol(SymbolOptions(
geometry: LatLng(37.7749, -122.4194),
iconImage: 'assets/icon_pin.png', // ensure added as style image first
iconSize: 1.2,
));
Add line layer (via geojson source) – see example app for full workflow.
Provide a styleString
:
- Remote URL (
https://.../style.json
) - App asset (
assets/map_style.json
with pubspec asset registration) - Local file system absolute path
- Raw JSON string
Embed the key directly in the vector tile URL:
https://tiles.example.com/{z}/{x}/{y}.vector.pbf?api_key=YOUR_KEY
Copy mbtiles (or sprites/glyphs) from bundled assets to a writable directory (e.g. cache) and point your style sources there. See issues #338 & #318 for community approaches.
The example app includes a pmtiles
usage sample demonstrating how to load datasets via a custom protocol handler / tile source. (Look for pmtiles.dart
in maplibre_gl_example
.)
Use data‑driven styling with expressions similar to Mapbox / MapLibre style spec. Some platform discrepancies exist (see FAQ around !has
). For cross‑platform safety prefer form: ["!", ["has", "field"]]
.
Layer/source property helpers & expression utilities are generated from templates under scripts/
. Do not edit generated files directly—run:
melos run generate
melos format-all
This repository is a multi-package workspace:
maplibre_gl
– main Flutter plugin (mobile/native bindings)maplibre_gl_web
– web implementationmaplibre_gl_platform_interface
– shared platform interface (enables adding alternative implementations)scripts/
– code generation templates & tooling
Most APIs are source-compatible. Key differences to watch:
- Dependency name changes to
maplibre_gl
. - Remove any Mapbox token initialization (MapLibre uses open assets or your own tile endpoints).
- If you referenced Mapbox-specific style endpoints, replace with self-hosted / MapLibre friendly ones.
- Audit expressions for iOS compatibility (
["!has", ...]
variant change noted in FAQ).
- Reuse a single
MapLibreMap
widget when possible instead of recreating it in tab/page switches. - Batch style mutations (e.g. add sources before adding dependent layers) to avoid intermediate layout recalculations.
- Use simpler geometries or tiling strategies for very dense data.
- Avoid large uncompressed GeoJSON inline; host as a URL source if size is large.
- Defer camera animations until style load (
onStyleLoadedCallback
).
If you embed API keys in style URLs for third-party tiles, ensure you:
- Restrict keys at the provider (domain / referer / usage caps).
- Avoid shipping unnecessary privileges (read-only tile scopes where possible). Environment variable injection at build time is recommended for CI-driven apps—avoid committing raw secrets.
Copy them to a writable directory (cache/documents) first, then reference the new path in the style or source configuration. See issues #338 & #318.
Ensure abiFilters
include the ABIs you intend to ship:
buildTypes {
release {
ndk { abiFilters 'armeabi-v7a','arm64-v8a','x86_64','x86' }
}
}
Add NSLocationWhenInUseUsageDescription
(see iOS setup).
Error: Invalid filter value: filter property must be a string
. Replace ["!has", "value"]
with ["!", ["has", "value"]]
.
- Minimal example (for pub.dev & quick start): see
example/lib/main.dart
- Full featured example app:
maplibre_gl_example
- API docs: https://pub.dev/documentation/maplibre_gl/latest/
- MapLibre upstream projects: maplibre-gl-js, maplibre-native
Releases follow semantic versioning as practical; breaking changes are documented in the CHANGELOG (root and per package). Always consult the changelog when upgrading.
This is a multi-package workspace managed by melos.
Basic flow:
dart pub global activate melos # activate melos package
melos bootstrap # initialize & link local packages
Then open & run the example app to validate and check changes.
Please read the full CONTRIBUTING.md before submitting a PR.
Generated code: Some API surface (layer/source property helpers, expression utilities) is produced via a generator under
scripts/
. Do not modify generated files directly — see Code Generation & Formatting section for workflow.
- Join our Slack channel
- StackOverflow tag: #maplibre
- Discussions: https://github.com/maplibre/flutter-maplibre-gl/discussions
- Bugs / Features: Open an issue (include reproduction details/logs where possible)
See LICENSE for details.
If this plugin helps you build something cool, consider starring the repo to support visibility.