Skip to content
Blog

OpenStreetMap in Flutter with flutter_map

Build an OpenStreetMap-powered map with flutter_map 8.3.2: MapOptions, TileLayer, markers, polygons, camera moves, and OSM tile policy compliance.

Published on • November 5, 2026

AI Assistant

OpenStreetMap data is free, but its tile servers are not a free-for-all: they are donation-funded, capacity-limited and actively policed. flutter_map gives you a pure-Flutter map client that can comply with those rules out of the box — if you configure it deliberately.

A map client with no vendor

flutter_map is at 8.3.2 (published August 27, 2026 by the verified fleaflet.dev publisher, BSD-3-Clause), with 8.4.0-dev.1 in prerelease. It runs on Android, iOS, Linux, macOS, web and Windows, ships 100% in Dart with no native map SDK, and pulls in latlong2 for coordinates plus path_provider for caching. The widget itself is minimal:

FlutterMap(
  options: MapOptions(
    initialCenter: const LatLng(51.505, -0.124),
    initialZoom: 13,
  ),
  children: [
    TileLayer(/* ... */),
  ],
)

The map expands to fill its constraints, so always keep it inside a bounded parent. MapOptions also accepts initialCameraFit (with CameraFit.bounds, insideBounds or coordinates plus padding and min/max zoom), cameraConstraint, minZoom/maxZoom, interactionOptions for gesture flags, and keepAlive for maps that live inside a PageView.

The base layer: TileLayer and OSM

flutter_map supplies no tiles, so you bring a URL template:

TileLayer(
  urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  userAgentPackageName: 'net.redlinesoft.myapp',
  maxNativeZoom: 19,
)

userAgentPackageName is not cosmetic. flutter_map sends flutter_map (<your package>) as the HTTP User-Agent, and OSM now returns a blocked tile to inadequately identified non-web clients. The docs are blunt about the scale of the problem: on 2025-06-09, flutter_map was the single largest User-Agent on the public servers at 1,610 tiles/second, and over 99 million daily tile requests came from unidentified apps. The OSM Tile Usage Policy also requires caching (honour HTTP headers or at least 7 days), HTTPS URLs, and no bulk downloading — flutter_map’s built-in caching, added in v8.2.0, exists to meet exactly that requirement.

Markers and polygons

MarkerLayer takes arbitrary widgets, not just bitmaps, and PolygonLayer draws filled areas with culling, batching and Ramer-Douglas-Peucker simplification (tolerance 0.5 by default) enabled:

MarkerLayer(
  markers: [
    Marker(
      point: const LatLng(51.505, -0.124),
      width: 80,
      height: 80,
      child: const Icon(Icons.location_on, size: 40, color: Colors.red),
    ),
  ],
),
PolygonLayer(
  polygons: [
    Polygon(
      points: [const LatLng(51.6, -0.2), const LatLng(51.4, -0.1), const LatLng(51.5, 0.0)],
      color: Colors.blue.withOpacity(0.3),
    ),
  ],
),

width and height on a marker are required for culling and positioning, defaulting to 30 logical pixels. Two gotchas from the docs: markers are culled offscreen (so StatefulWidget children lose state), and keyed markers crash when the same marker is visible across multiple worlds.

Moving the camera

Programmatic control is imperative through MapController, while MapCamera is the read-only viewport. Outside the map, create and attach a controller; inside a layer, use the context helpers:

final _mapController = MapController();

FlutterMap(
  mapController: _mapController,
  // ...
);

// later, e.g. from a button:
_mapController.move(const LatLng(51.505, -0.124), 9);
final zoom = _mapController.camera.zoom;

fitCamera handles padded bounding-box framing and rotate sets degrees from north. Two caveats: the controller is not safe to use until the first build completes (use MapOptions.onMapReady, and never call it inside onMapReady itself), and controller moves are instant — for animation, the community flutter_map_animations plugin is the recommended path.

Playing by the OSM tile rules

Attribution is mandatory and must stay visible: ”© OpenStreetMap contributors”, never hidden behind toggles or off-screen. The policy forbids prefetching, “download city for offline” features and pre-seeding tile archives on tile.openstreetmap.org, and access may be blocked without notice. For real offline support, ship your own tiles instead:

// From bundled assets:
TileLayer(
  tileProvider: AssetTileProvider(),
  urlTemplate: 'assets/map/{z}/{x}/{y}.png',
)

The filesystem variant uses FileTileProvider(), and flutter_map_mbtiles (1.1.0, published October 31, 2026) reads standard MBTiles archives for a 100% offline map. Alternative hosted OSM-derived tile servers carry their own, often more permissive, terms.

Takeaway: pair flutter_map 8.3.2 with urlTemplate, a real userAgentPackageName and RichAttributionWidget to stay inside the OSM tile policy, then reach for AssetTileProvider, FileTileProvider or MBTiles when your app must work offline.