Skip to content
Blog

Mapbox in Flutter: Styling Custom Maps with mapbox_gl

Pick between community mapbox_gl and the official mapbox_maps_flutter SDK, then wire up tokens, style URLs, annotations, and Mapbox's free tier.

Published on • November 5, 2026

AI Assistant

Mapbox renders fast, styleable vector maps, but Flutter has two very different plugins for the job: one is a community fork that has not shipped since 2022, the other is the SDK Mapbox itself maintains. Choosing wrong means a rewrite a few months later.

Two plugins, one brand

The classic result on pub.dev is mapbox_gl at 0.16.0, published May 19, 2022 by an unverified uploader under BSD-2-Clause for Android, iOS and web. Its README states plainly that the project is “community driven and is not an official Mapbox product”. It wraps mapbox-gl-native on mobile and mapbox-gl-js on web, and its supported-API matrix covers style, camera, gestures, user location, symbol/circle/line/fill annotations and expressions, while heatmap layers and the style DSL are marked unsupported on every platform.

The maintained choice is mapbox_maps_flutter at 3.34.2, published November 3, 2026 by the verified mapbox.com publisher with a 3.35.0-rc.1 prerelease already available. It adds a MapWidget rendering MVT maps natively, layer types including circle, line, fill, raster, heatmap, hillshade, sky and 3D fill-extrusion, marker clustering, a live location puck, and offline caching plus snapshots on Android and iOS. Requirements are Flutter 3.38.1 / Dart 3.10.0, Android minSdk 21 and iOS 14+.

If you want Mapbox-shaped tiles without a Mapbox account, maplibre_gl (0.28.0, verified maplibre.org publisher) is the open-source relative.

Configuring access tokens

mapbox_gl needs two tokens. Mobile builds require a secret token with the Downloads: Read scope so the native SDKs can be fetched; without it the build fails with SDK Registry token is null on Android or a 401 Unauthorized from the iOS pod installer. A separate public token is passed to the widget, and the README recommends injecting it at build time instead of hardcoding:

// flutter run --dart-define ACCESS_TOKEN=YOUR_TOKEN_HERE
MapboxMap(
  accessToken: const String.fromEnvironment('ACCESS_TOKEN'),
  // ...
)

The official SDK consolidates this into one call at startup, before any map is created:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MapboxOptions.setAccessToken(const String.fromEnvironment('ACCESS_TOKEN'));
  runApp(const MyApp());
}

On web nothing else is needed because Mapbox GL JS loads automatically, though a strict Content-Security-Policy must allow api.mapbox.com script and style sources.

If you switch on the location component, the map also needs location permission — the plugin reads position but never prompts for it:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

On Android those must be requested at runtime from API 23 onwards (the example app uses the location plugin for this), and iOS expects NSLocationWhenInUseUsageDescription in Info.plist with an explanation such as “Shows your location on the map and helps improve the map”.

Styles, annotations and camera moves

mapbox_gl accepts a style through styleString in four formats: a URL (a built-in style constant from MapboxStyles, or any mapbox:// or http(s):// style), a local asset, a local file path, or raw style JSON (Android only). Once the style is loaded, MapboxMapController exposes annotation managers and camera methods:

onMapCreated: (MapboxMapController controller) async {
  await controller.addSymbol(SymbolOptions(
    geometry: const LatLng(37.7749, -122.4194),
    textField: 'Hello Mapbox',
    textSize: 14,
  ));

  // moveCamera() snaps instantly and can look jarring;
  // the API docs recommend animateCamera() instead.
  await controller.animateCamera(
    CameraUpdate.newLatLngZoom(const LatLng(37.7749, -122.4194), 14),
  );

  controller.onSymbolTapped.add((symbol) => debugPrint('${symbol.id} tapped'));
}

The same controller offers addCircle, addLine, addFill, GeoJSON sources, style layers such as addSymbolLayer, and moveCamera for instant jumps. Offline, downloadOfflineRegionStream takes an OfflineRegion with bounds, zoom range and mapStyleUrl and reports Success/InProgress/Error events.

The flutter_map alternative

If you only need Mapbox artwork rather than the native SDK, flutter_map 8.3.2 can display Mapbox tiles as plain raster images. The documented route is to share a Studio style and copy the Third Party / CARTO integration URL into a TileLayer — those requests consume the Static Tiles API quota. Two details matter: Mapbox tops out at zoom 22, so set maxNativeZoom, and the @2x suffix in the URL forces retina tiles on every display. Mapbox attribution is extensive and requires both logo and text, so plan for a RichAttributionWidget.

What Mapbox costs

Mapbox accounts are free to create and pricing is pay-as-you-go with automatic volume discounts, per the Mapbox pricing page. The Maps SDKs for Mobile include the first 25,000 monthly active users free, then $4.00 per 1,000 MAUs in the 25,001-125,000 band; Mapbox GL JS on web includes the first 50,000 map loads per month free, then $5.00 per 1,000 loads. Budget for growth before shipping a popular app, and size offline regions with Mapbox’s estimator before downloading them.

Takeaway: build new Mapbox apps on the official mapbox_maps_flutter (3.34.2) with a --dart-define access token, treat mapbox_gl (0.16.0, May 2022) as legacy, and fall back to flutter_map with a Mapbox raster URL only when you need tiles without the native SDK.