Maps are honestly one of the trickiest things to ship in a cross-platform .NET MAUI app — and I say that as someone who's burned a weekend more than once chasing a blank gray tile on Android. The built-in Microsoft.Maui.Controls.Maps control gives you a Google Map on Android and an Apple Map on iOS for free, but it stops short of clustering, custom tiles, offline support, and the modern styling APIs you'll probably end up wanting.
In 2026, most production teams pick one of three paths: stay with the official Maps control, switch to a Mapbox or Google Maps SDK binding for richer features, or render OpenStreetMap tiles with a free community library.
So, let's dive in. This guide walks through all of them for .NET MAUI 10, with working code, plus the platform configuration you usually forget about until your app crashes on first launch.
Quick comparison: which map should you pick?
Before writing a single line of code, decide on the SDK based on your real requirements — not the ones you think you'll need in six months. Most teams over-engineer this and pay for it later.
- Microsoft.Maui.Controls.Maps — Free, official, very limited. Great for a "show a pin on a map" use case. No clustering, no custom tile servers, and basic styling only on Android.
- Google Maps SDK (Android) + MapKit (iOS) — Full feature parity with the native SDKs: clustering, heatmaps, custom markers, ground overlays. You write a small handler per platform. Google bills for Map Loads above the free tier (28,500 free loads/month as of 2026).
- Mapbox Maps SDK v11 — Best for custom cartography, vector tiles, 3D terrain, and a single styling system that works on both platforms. Pricing is per Monthly Active User (MAU), with 25,000 MAUs free.
- OpenStreetMap via Mapsui or BruTile — Free, no API keys, no MAU limits. Great for kiosks and internal apps. Raster tile rendering is heavier on memory than vector tiles, though.
If you only need to display a pin and an address, honestly, stop reading and use Microsoft.Maui.Controls.Maps. If you need clustering of more than 200 markers, skip straight to the Google Maps SDK or Mapbox.
Option 1: The built-in Microsoft.Maui.Controls.Maps
The official control is the fastest way to get a map on screen. In .NET MAUI 10, it ships as a separate NuGet package — it's no longer in the default MAUI workload (a small surprise that catches a lot of people upgrading from .NET 9).
1. Install the NuGet and register the handler
dotnet add package Microsoft.Maui.Controls.Maps --version 10.0.0
In MauiProgram.cs, chain .UseMauiMaps() after .UseMauiApp<App>():
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiMaps()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
2. Add the Google Maps API key (Android only)
iOS uses Apple Maps with no key required. Android, on the other hand, needs a Google Maps API key in Platforms/Android/AndroidManifest.xml:
<application>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY_HERE" />
</application>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
For iOS, add the location usage strings to Platforms/iOS/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to show nearby places on the map.</string>
3. Show the map and add a pin
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="http://schemas.microsoft.com/dotnet/2021/maui/maps"
x:Class="MyApp.MapsPage">
<maps:Map x:Name="map" IsShowingUser="True" MapType="Street">
<maps:Map.Pins>
<maps:Pin Label="Microsoft"
Address="Redmond, WA"
Type="Place"
Location="47.6396,-122.1300" />
</maps:Map.Pins>
</maps:Map>
</ContentPage>
To center the map on a specific area at startup, set the visible region in code-behind:
public MapsPage()
{
InitializeComponent();
var center = new Location(47.6396, -122.1300);
var span = MapSpan.FromCenterAndRadius(center, Distance.FromKilometers(2));
map.MoveToRegion(span);
}
That's the entire happy path. Where it falls apart: there's no clustering API, custom markers require a handler override, and you can't style the Apple Map at all (yes, still, in 2026).
Option 2: Google Maps SDK with a custom handler
If you need clustering, custom markers, polylines with arrows, or heatmaps, you'll want to write a handler that wraps the native Google Maps SDK on Android and MapKit on iOS. It's more work, but you get complete control.
1. Create a cross-platform map view
public class AdvancedMap : View
{
public static readonly BindableProperty PinsProperty =
BindableProperty.Create(nameof(Pins), typeof(IList<MapPin>), typeof(AdvancedMap));
public IList<MapPin> Pins
{
get => (IList<MapPin>)GetValue(PinsProperty);
set => SetValue(PinsProperty, value);
}
public static readonly BindableProperty EnableClusteringProperty =
BindableProperty.Create(nameof(EnableClustering), typeof(bool), typeof(AdvancedMap), true);
public bool EnableClustering
{
get => (bool)GetValue(EnableClusteringProperty);
set => SetValue(EnableClusteringProperty, value);
}
}
public record MapPin(double Lat, double Lon, string Title, string? Subtitle = null);
2. Android handler using Google Maps SDK v18
Install the binding NuGets:
dotnet add package Xamarin.GooglePlayServices.Maps --version 121.0.0
dotnet add package Xamarin.GooglePlayServices.Maps.Utils --version 3.10.0
Then create the handler:
#if ANDROID
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.Gms.Maps.Utils.Clustering;
using Microsoft.Maui.Handlers;
public partial class AdvancedMapHandler : ViewHandler<AdvancedMap, MapView>
{
GoogleMap? _googleMap;
ClusterManager? _clusterManager;
protected override MapView CreatePlatformView()
{
var mapView = new MapView(Context!);
mapView.OnCreate(null);
mapView.OnResume();
mapView.GetMapAsync(new OnMapReadyCallback(OnMapReady));
return mapView;
}
void OnMapReady(GoogleMap map)
{
_googleMap = map;
if (VirtualView.EnableClustering)
{
_clusterManager = new ClusterManager(Context, map);
map.SetOnCameraIdleListener(_clusterManager);
map.SetOnMarkerClickListener(_clusterManager);
}
AddPins();
}
void AddPins()
{
if (_googleMap == null) return;
if (_clusterManager != null)
{
foreach (var pin in VirtualView.Pins)
_clusterManager.AddItem(new ClusterItem(pin.Lat, pin.Lon, pin.Title));
_clusterManager.Cluster();
}
else
{
foreach (var pin in VirtualView.Pins)
_googleMap.AddMarker(new MarkerOptions()
.SetPosition(new LatLng(pin.Lat, pin.Lon))
.SetTitle(pin.Title));
}
}
}
#endif
3. iOS handler using MapKit
#if IOS
using MapKit;
using CoreLocation;
using Microsoft.Maui.Handlers;
public partial class AdvancedMapHandler : ViewHandler<AdvancedMap, MKMapView>
{
protected override MKMapView CreatePlatformView()
{
var map = new MKMapView();
// iOS 17+ gives us native marker clustering for free.
return map;
}
protected override void ConnectHandler(MKMapView platformView)
{
base.ConnectHandler(platformView);
foreach (var pin in VirtualView.Pins)
{
var annotation = new MKPointAnnotation
{
Coordinate = new CLLocationCoordinate2D(pin.Lat, pin.Lon),
Title = pin.Title,
Subtitle = pin.Subtitle
};
platformView.AddAnnotation(annotation);
}
}
}
#endif
4. Register the handler
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<AdvancedMap, AdvancedMapHandler>();
});
And that's it — you've got a single XAML control that uses the full native SDK on each platform. Clustering works out of the box on both, and you can extend the handler with polylines, heatmaps, and custom marker views as you need them.
Option 3: Mapbox Maps SDK v11 for custom cartography
Mapbox is the right choice when design wants a unique map style — dark mode that matches your brand, hidden labels, satellite with custom overlays, or 3D buildings. Mapbox v11 (released late 2025) introduced a Lights API for time-of-day rendering and improved style performance by roughly 30% over v10.
Mapbox doesn't ship official .NET MAUI bindings, but the community Mapbox.Maui package wraps the native SDKs nicely:
dotnet add package Mapbox.Maui --version 11.8.0
Initialize it in MauiProgram.cs with your public token:
builder.UseMapboxMaui("pk.ey...your_public_token");
Render a styled map:
<mb:MapboxView x:Name="mapbox"
xmlns:mb="http://mapbox.maui"
MapboxStyle="mapbox://styles/mapbox/dark-v11"
CameraOptions="{Binding Camera}" />
And the view-model:
public CameraOptions Camera => new()
{
Center = new Position(47.6396, -122.1300),
Zoom = 12,
Pitch = 45,
Bearing = 30
};
One gotcha: for Mapbox you also need to add the secret MAPBOX_DOWNLOADS_TOKEN as an environment variable so MSBuild can fetch the iOS framework during build. This trips up nearly every first-time setup — check the build log for "Authentication required" if your iOS build fails. (I lost about two hours to this the first time. Don't be me.)
Option 4: OpenStreetMap with Mapsui (free, no API key)
For internal tools, kiosks, and apps that can't use Google or Apple services, Mapsui renders OpenStreetMap raster tiles with no API key at all:
dotnet add package Mapsui.Maui --version 5.0.0
<mapsui:MapControl x:Name="mapControl"
xmlns:mapsui="clr-namespace:Mapsui.UI.Maui;assembly=Mapsui.UI.Maui" />
public MapsuiPage()
{
InitializeComponent();
mapControl.Map.Layers.Add(Mapsui.Tiling.OpenStreetMap.CreateTileLayer());
var center = SphericalMercator.FromLonLat(-122.13, 47.6396).ToMPoint();
mapControl.Map.Navigator.CenterOnAndZoomTo(center, mapControl.Map.Navigator.Resolutions[14]);
}
Just remember OpenStreetMap's tile usage policy: bulk downloads are forbidden, and high-traffic apps must either run their own tile server or use a commercial provider like Geoapify or MapTiler. The OSM community is generous, but they're not running infrastructure to host your viral app for free.
Common pitfalls and how to fix them
Blank gray map on Android
Ninety percent of the time, this is one of three things: the API key is missing or restricted to the wrong package name, billing isn't enabled on the Google Cloud project, or the MapsInitializer renderer isn't being created. Open adb logcat and search for Authorization failure — the message tells you exactly which key check failed.
Map flickers when scrolling inside a CollectionView
Map views are expensive to recycle. Never put a map inside a list cell. If you absolutely must, set HeightRequest explicitly and use ItemSizingStrategy="MeasureFirstItem" so the layout pass doesn't invalidate the map on every scroll.
iOS map shows but no pins appear
MapKit only renders annotations whose coordinate is inside the visible region after the map has laid out. Call SetRegion with a bounding box that contains your pins before adding them — or call it again inside the RegionChanged event the first time it fires.
App crashes on Android 14 with SecurityException
Android 14 (API 34) requires the ACCESS_BACKGROUND_LOCATION permission to be requested separately, after the user grants foreground location. Use Permissions.RequestAsync<Permissions.LocationAlways>() only after you already have LocationWhenInUse. Doing it the other way around will crash on launch.
Performance tips for production map apps
- Cluster aggressively. Past ~150 markers, individual pin rendering becomes the bottleneck. Cluster everything by default and only show individual pins beyond zoom level 15.
- Cache geocoding results. Each call to
Geocoder.GetLocationsAsyncon Android hits a rate-limited service. Cache the result keyed by the normalized address string. - Disable buildings and traffic when not needed. Both layers cause ongoing tile fetches even when the map is stationary — easy battery drain.
- Reuse marker bitmaps. Calling
BitmapDescriptorFactory.FromResourcefor every marker leaks memory fast. Cache theBitmapDescriptorper icon type. - Detach the map in OnDisappearing. Especially on Android, retaining a map across navigation events is the single biggest source of memory leaks in MAUI map apps. I've seen this one bite even experienced teams.
FAQ
Does .NET MAUI have a built-in map control?
Yes, but it ships as a separate NuGet package called Microsoft.Maui.Controls.Maps in .NET MAUI 10. You install it, add .UseMauiMaps() in MauiProgram.cs, configure a Google Maps API key on Android, and add location usage strings to Info.plist on iOS. It renders Google Maps on Android and Apple Maps on iOS.
How do I add a Google Maps API key to a .NET MAUI app?
Open Platforms/Android/AndroidManifest.xml and add a <meta-data> element with name com.google.android.geo.API_KEY inside the <application> tag. Restrict the key in the Google Cloud Console to your Android package name and SHA-1 signing certificate fingerprint to prevent abuse. iOS uses Apple Maps by default and doesn't need a key unless you switch to the Google Maps iOS SDK.
Can I use Mapbox in .NET MAUI?
Yes. There's no official Microsoft binding, but the community-maintained Mapbox.Maui NuGet package (currently at v11.8, aligned with Mapbox SDK v11) wraps the native Android and iOS SDKs. You'll need both a public token and a secret downloads token, with the downloads token exported as an environment variable so MSBuild can fetch the iOS framework during build.
How do I cluster pins on a .NET MAUI map?
The built-in Microsoft.Maui.Controls.Maps doesn't support clustering. On Android, install Xamarin.GooglePlayServices.Maps.Utils and use the ClusterManager class inside a custom handler. On iOS 17 and later, MapKit clusters annotations automatically when you set ClusteringIdentifier on your MKMarkerAnnotationView subclass. Mapbox handles clustering natively through its style spec.
Why is my map blank on Android?
A blank gray tile area on Android almost always means the API key is wrong, restricted to the wrong package name or SHA-1, or that billing isn't enabled on the Google Cloud project. Check adb logcat for an "Authorization failure" message — Google logs the exact reason. The Maps SDK for Android still requires a billing account to be attached, even when your usage stays comfortably within the free tier.
Wrapping up
Pick the simplest option that meets your requirements. The built-in Microsoft.Maui.Controls.Maps is genuinely enough for most apps that show a handful of pins. Move to a custom handler around Google Maps SDK and MapKit when you need clustering or heatmaps. Reach for Mapbox when design needs custom cartography, and use Mapsui with OpenStreetMap for offline-friendly or no-API-key scenarios.
Whatever you pick, configure location permissions correctly from day one — most production map bugs trace back to permission handling rather than the map itself. Trust me on that one.