If you need a Capacitor Google Maps alternative, you have three realistic paths: keep the official @capacitor/google-maps plugin and work around its limits, render the map inside the WebView with a JavaScript library such as MapLibre GL JS or Leaflet, or use a native map plugin built on another SDK. Capgo does not ship its own map rendering plugin, so this guide compares the options neutrally, and shows the Capgo plugins that work well next to any map: navigation handoff, geocoding and background location.
Why developers look beyond @capacitor/google-maps
The official plugin (version 8 for Capacitor 8) embeds the native Google Maps SDK on iOS and Android and the Google Maps JavaScript API on the web. It works, but these points come up again and again.
A billing enabled Google Cloud account on every platform
The plugin needs API keys tied to a Google Cloud account with billing enabled, on Android, iOS and web. Google’s pricing for native mobile map loads is generous, but you still have to set up billing, restrict keys per platform, and watch quotas. Web map loads and services such as Places and Directions are billed by usage after free caps. Check the current Google Maps Platform pricing page before you commit, as the model changed in 2025.
On Android the map is drawn behind the WebView
On iOS the map view is placed into the WebView’s scroll view. On Android it is rendered beneath the entire WebView, and the plugin moves it to follow the <capacitor-google-map> element. Every layer above it must be transparent:
/* Required on Android with @capacitor/google-maps */
html,
body,
ion-content,
ion-content::part(background) {
background: transparent !important;
--background: transparent;
}
This is the number one “my map is blank” issue. It also makes custom overlays, modals and page transitions harder, because your HTML sits on top of a native view.
Markers can be added and removed, not updated
The API has addMarker, addMarkers, removeMarker and removeMarkers, but no updateMarker. A live vehicle position means remove plus add on every update, which can flicker and costs bridge calls.
Limited styling and features on native
Some options are web only, for example mapId for cloud based styling, and region and language. You get markers, clustering, polylines, polygons, circles, tile overlays, camera control and click listeners. If you need data driven styling, heatmaps or 3D buildings in the same way on all platforms, you will hit limits.
The options at a glance
| Option | Rendering | API key needed | Tile source | Best for |
|---|---|---|---|---|
@capacitor/google-maps |
Native SDK (Android, iOS), JS on web | Yes, billing enabled | Apps that want Google data and native performance | |
| MapLibre GL JS | WebGL in the WebView | Depends on tile provider | Any vector tile source, self-hosted possible | Custom styled maps, offline-ish, no vendor lock in |
| Leaflet | DOM and canvas in the WebView | Depends on tile provider | Raster tiles (OSM based or others) | Simple maps, small bundle, many plugins |
| Google Maps JS API in WebView | Google’s JS renderer | Yes | Same Google look on all platforms, full JS feature set | |
| Native MapLibre or Mapbox plugins | Native SDK | Depends | Any / Mapbox | Heavy data, long sessions, native gestures |
There is no single winner. The right choice depends on data volume, styling needs, and how much you want to depend on one vendor.
Option 1: MapLibre GL JS in the WebView
MapLibre GL JS is the open source fork of Mapbox GL JS v1. It renders vector tiles with WebGL and runs well in WKWebView and the Android System WebView. It is the most flexible option if you do not need Google’s data.
bun add maplibre-gl
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
const map = new maplibregl.Map({
container: 'map',
style: 'https://your-tile-provider.example.com/styles/streets/style.json',
center: [2.3522, 48.8566], // [lng, lat]
zoom: 12,
});
map.addControl(new maplibregl.NavigationControl(), 'top-right');
// A marker you can move without remove/add
const courier = new maplibregl.Marker({ color: '#1d4ed8' })
.setLngLat([2.35, 48.85])
.addTo(map);
export function moveCourier(lng: number, lat: number) {
courier.setLngLat([lng, lat]);
}
GeoJSON layers, data driven styling and route lines are first class:
map.on('load', () => {
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: [
[2.3522, 48.8566],
[2.3376, 48.8606],
[2.2945, 48.8584],
],
},
},
});
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
paint: { 'line-color': '#2563eb', 'line-width': 5 },
});
});
Things to know:
- You need a tile source. Options include commercial vector tile hosts, or self-hosting tiles generated from OpenStreetMap data. The public OpenStreetMap tile servers have a usage policy that does not allow heavy app traffic.
- Always show the attribution your tile provider requires.
- Because it is plain web code, the same map works in a browser preview, which speeds up development.
Option 2: Leaflet
Leaflet is small and mature. It is a good fit for maps with a few hundred markers, raster tiles and simple interactions.
bun add leaflet
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
const map = L.map('map').setView([48.8566, 2.3522], 13);
L.tileLayer('https://tiles.example.com/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors',
}).addTo(map);
L.marker([48.8584, 2.2945]).addTo(map).bindPopup('Eiffel Tower');
In bundled apps, Leaflet’s default marker icon paths often break. Import the icon images and set L.Icon.Default options, or use your own L.divIcon.
Option 3: Google Maps JavaScript API in the WebView
If you need Google’s data and look, but want the map to behave like any other HTML element, load the Maps JavaScript API in the WebView instead of the native SDK. You lose some native smoothness, and every map load is a billable web load, but you get the full JS API: cloud styling with map IDs, advanced markers, data layers.
Restrict the web key by HTTP referrer. On iOS, Capacitor serves the app from capacitor://localhost by default, and on Android from https://localhost, so add those origins to the key restrictions and test both.
Option 4: Native plugins built on other SDKs
Native wrappers around MapLibre Native or the Mapbox SDKs exist in the community. They give native gestures and performance with any tile source. Before choosing one, check that it supports Capacitor 8, has a recent release, supports Swift Package Manager on iOS (the default for new Capacitor 8 iOS projects), and covers the overlay types you need. Native map plugins share the same layering trade-off as the official plugin: the map is a native view, and your HTML has to be laid out around it.
Keep @capacitor/google-maps if
- You need Google’s base map, Places and Street View data.
- Your map is a full screen view, not a small card inside scrolling content.
- You can live with remove and add for moving markers, or update positions rarely.
If you keep it, set the transparency CSS above, give the <capacitor-google-map> element an explicit size, call destroy() when leaving the page, and set skipLibCheck: true in tsconfig.json as the plugin’s README requires.
Plugins that pair well with any map
Whatever renders the map, these Capgo plugins cover the native parts that a WebView map cannot do on its own.
Hand off turn by turn navigation
Building navigation is a project of its own. Most apps open the user’s preferred navigation app instead. @capgo/capacitor-launch-navigator supports Apple Maps, Google Maps, Waze and many others:
import { LaunchNavigator, TransportMode } from '@capgo/capacitor-launch-navigator';
await LaunchNavigator.navigate({
destination: [48.8584, 2.2945], // [lat, lng]
options: { transportMode: TransportMode.DRIVING },
});
Geocode without a web service
@capgo/capacitor-nativegeocoder uses the platform geocoders on iOS and Android, which do not need a Google key on device:
import { NativeGeocoder } from '@capgo/capacitor-nativegeocoder';
const { addresses } = await NativeGeocoder.reverseGeocode({
latitude: 48.8584,
longitude: 2.2945,
maxResults: 1,
});
console.log(addresses[0]?.thoroughfare, addresses[0]?.locality);
const result = await NativeGeocoder.forwardGeocode({
addressString: '5 Avenue Anatole France, Paris',
maxResults: 1,
});
console.log(result.addresses[0]?.latitude, result.addresses[0]?.longitude);
On the web, the plugin uses Google’s API and needs the apiKey option.
Show the user’s position, even in the background
For a foreground “blue dot”, @capacitor/geolocation is enough. To keep tracking while the app is in the background, or to trigger events when users enter places, use @capgo/background-geolocation, and pass points to whichever map you chose.
Troubleshooting map issues in Capacitor
Blank map on Android with the official plugin. Transparency is missing on a parent element. Inspect with Chrome DevTools via chrome://inspect and look for any background color between <html> and the map element.
Map element has zero height. Map containers have no intrinsic size. Set an explicit height, or use flex layout with a fixed parent height.
WebGL map is blurry or slow on Android. Check that the System WebView is up to date on the device, reduce the number of layers, and avoid re-creating the map on every route change. Create it once and reuse it.
Tiles do not load in release builds. Check your Content Security Policy and network security config, and make sure the tile host is HTTPS.
Map breaks after you change the tile style URL. Style and tile URLs are just JavaScript config. You can switch providers for existing users with Capgo live updates, without an app store release, as long as no native code changes.
How to decide
- Need Google data and native performance on a full screen map: keep
@capacitor/google-maps. - Need custom styling, no Google billing, and a map that behaves like HTML: MapLibre GL JS.
- Need a simple map with a few markers: Leaflet.
- Need Google’s look in a scrolling layout: Google Maps JS API in the WebView.
Whichever you pick, test on a low-end Android device early. For more native building blocks, browse the Capgo plugin directory.