Getting Started
Add maplibre_gl to a Flutter app and get a map on screen. Android and iOS need
one platform file each; web needs nothing to start. Later sections cover the web
setups that need more: a Content-Security-Policy, a self-hosted copy of the
library, or your own JS interop.
Add the dependency
Using the development version
To get the latest features and fixes before they are published, depend on the package directly from GitHub.
Warning
The development version is not considered stable and shouldn't be used in production.
Use it as a normal dependency, or temporarily override it under
dependency_overrides::
Android
If you want to show the user's location on the map, add location permissions to the application manifest:
<manifest>
<!-- Always include this permission -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Include only if your app benefits from precise location access -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest>
For F-Droid and other builds without Google Play Services, set maplibre_gl.playServicesLocation=false in android/gradle.properties, as shown in User Location.
The map renders with OpenGL ES. To use Vulkan, set maplibre_gl.renderer in the same file, as shown in Rendering backend.
iOS
Add a location usage description to your Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Show your location on the map</string>
Swift Package Manager
The plugin is a Swift package, and Flutter builds it through its Swift Package Manager integration, on by default since Flutter 3.44. A new app needs nothing more. It takes Xcode 15 or later.
If your app turned Swift Package Manager off (flutter config --list shows enable-swift-package-manager: false), turn it back on for all your projects:
or for this app only, in pubspec.yaml, which also covers CI and the other developers:
The next flutter run or flutter build ios adds the Swift packages to ios/Runner.xcodeproj: commit that change. Plugins without a Swift package keep coming from CocoaPods, so the Podfile stays while the app has any. Once every plugin is a Swift package, Flutter suggests removing CocoaPods: run pod deintegrate in ios/ and delete the Podfile.
Add-to-app
A Flutter module inside a native iOS app still uses CocoaPods, since Flutter does not build modules with Swift Package Manager yet (flutter/flutter#146957). The plugin keeps its podspec for these apps, on MapLibre iOS 7.0.0: later versions are not published to CocoaPods.
Web
On web, the plugin renders with MapLibre GL JS, and it loads that library itself: nothing needs to be added to web/index.html. The plugin imports the exact build it is tested against, stylesheet included, before the first map is built.
Upgrading from an older version
If your index.html still has the maplibre-gl.js script and maplibre-gl.css link tags from an earlier setup, remove them. An existing maplibregl global is reused as it is, so a manually pinned copy silently overrides the version the plugin is tested against.
Basic Usage
import 'package:flutter/material.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class MapPage extends StatelessWidget {
const MapPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: MapLibreMap(
initialCameraPosition: const CameraPosition(
target: LatLng(51.5, -0.09),
zoom: 11,
),
styleString: MapLibreStyles.demo,
),
);
}
}
Style URL
Pass any MapLibre-compatible style URL to styleString: a hosted provider such as
MapTiler or Protomaps, your own
tile server, or a style JSON shipped as an asset. See Map Styles.
Web: advanced setup
Requirements
The browser has to provide WebGL2. MapLibre GL JS 6 draws with it and no longer falls back to WebGL1, so a browser without it shows no map at all. Two cases end differently:
- The browser has no WebGL2 at all, as on Safari and iOS before 15, and on Chrome and Firefox from before 2017. Flutter's own renderer asks for WebGL1 on such a browser, so the app around the map keeps working and only the map goes missing, which is what makes this easy to miss. The plugin logs the cause when this happens.
- WebGL2 exists but no context can be created, because the browser blocklists it for the GPU driver, or hardware acceleration is switched off. Flutter's renderer asks for WebGL2 there, and does not fall back either, so the whole app stays blank and the library version makes no difference.
Only the first case has a way out: MapLibre GL JS 5, the last major version with
the WebGL1 fallback. Point the plugin at a version 5 copy with
MapLibreMap.webLibrarySource, exactly as for
self-hosting. The plugin keeps working against
it, but you then stay off the version it is tested against.
Content-Security-Policy
Skip this if your app has no CSP. If it does, three things need allowing: the
plugin imports the library, which a CSP governs like any other script; the style
and the tiles are fetched; and MapLibre GL JS runs its tile work in a Web Worker.
That worker is built from a blob: URL whenever the library is loaded
cross-origin, which is what a CDN is.
script-src 'self' https://unpkg.com ;
connect-src 'self' https://unpkg.com https://your.tile.host ;
worker-src 'self' blob: ;
img-src data: blob: 'self' ;
Self-hosting the library makes the worker same-origin, so blob: is not needed in worker-src and the CDN host drops out of script-src and connect-src.
Self-hosting MapLibre GL JS
If a Content-Security-Policy rules out the CDN, or you prefer serving the library yourself (for example as web assets), point the plugin at your copy before the first map is built:
void main() {
MapLibreMap.webLibrarySource = const MapLibreJsSource.urls(
scriptUrl: 'https://your.host/maplibre-gl.mjs',
styleUrl: 'https://your.host/maplibre-gl.css',
);
runApp(const MyApp());
}
MapLibre GL JS 6 is an ES module, so scriptUrl points at the .mjs build. Serve the whole dist directory, not just that one file: the library resolves its worker relative to its own URL, so the worker build has to sit next to it. Check too that your server answers .mjs with a JavaScript MIME type, text/javascript: a module script is refused outright when the type is something else, such as the application/octet-stream some servers still default to.
If the page loads MapLibre GL JS itself, set MapLibreMap.webLibrarySource = const MapLibreJsSource.preloaded(): the plugin then imports nothing and waits for the maplibregl global. An ES module defines no global on its own, so the page has to publish one:
<script type="module">
globalThis.maplibregl = await import('/your/path/maplibre-gl.mjs');
</script>
Calling MapLibre GL JS yourself
Because the library is loaded by the plugin, the maplibregl global no longer exists at page parse time. An app that calls into MapLibre GL JS with its own JS interop, for example to register a custom protocol with addProtocol, must await MapLibreMap.ensureWebLibraryLoaded() first:
Future<void> main() async {
if (kIsWeb) {
await MapLibreMap.ensureWebLibraryLoaded();
// maplibregl is now usable from JS interop.
}
runApp(const MyApp());
}
On Android and iOS MapLibreMap.ensureWebLibraryLoaded() completes immediately, so it is safe to await unconditionally.
PMTiles on web
To read PMTiles sources on web, load the pmtiles script in index.html and register the protocol from Dart. The registration used to be an inline script in index.html, but it needs the maplibregl global, which no longer exists at page parse time, so it moved into main() behind MapLibreMap.ensureWebLibraryLoaded():
<head>
<!-- ...existing head tags... -->
<script src="https://unpkg.com/pmtiles@4.4.0/dist/pmtiles.js"></script>
</head>
Future<void> main() async {
if (kIsWeb) {
await MapLibreMap.ensureWebLibraryLoaded();
registerPmTilesProtocol('https://your.host/archive.pmtiles');
}
runApp(const MyApp());
}
registerPmTilesProtocol is a small piece of JS interop around maplibregl.addProtocol and the pmtiles global. See pmtiles_protocol_web.dart in the example app for a complete implementation, including the conditional import that keeps the app compiling for Android and iOS.
Minimum versions
| Platform | Minimum version |
|---|---|
| Flutter | 3.44.0 |
| Dart | 3.12.0 |
| Android | API 24 (Android 7.0) |
| iOS | iOS 15.5 |
| Web | WebGL2, see Requirements |
| JDK | 21 |
The JDK version is for building: the Android side targets Java 21, so JDK 17 fails with invalid source release: 21.