---
title: A Google Maps Alternative for Capacitor Apps
description: Looking for a Capacitor Google Maps alternative? Compare @capacitor/google-maps with the MapLibre plugin — no billing account, no API key, Web support.
date: 
  created: 2026-08-21
  updated: 2026-08-21
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor MapLibre: sdks/capacitor/maplibre.md
faq: true
---

# A Google Maps Alternative for Capacitor Apps

The most common Capacitor Google Maps alternative is MapLibre, an open-source map renderer that works without a vendor account. Developers usually start looking for one after running into the same three things with `@capacitor/google-maps`: it needs an API key from a project with billing enabled on Android, iOS, and Web, the Android map is drawn behind the WebView, and markers can be added and removed but never updated. This post compares it with the [Capacitor MapLibre plugin](../../sdks/capacitor/maplibre.md) row by row, shows the code, and says where Google Maps is still the better pick.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

## Key Takeaways

- `@capacitor/google-maps` requires API keys from an account with billing enabled on Android, iOS, and Web.
- Google Maps Platform replaced the $200 monthly credit with per-SKU free usage caps on March 1, 2025: 10,000 free billable events per month for Essentials, 5,000 for Pro, 1,000 for Enterprise. Maps SDK usage on mobile stayed unlimited.
- On Android, the official plugin renders the map beneath the WebView, so the layers above it have to be transparent.
- `@capacitor/google-maps` offers `addMarker` and `removeMarker`, but no method to update a marker already on the map.
- The Capacitor MapLibre plugin needs no Google billing account and no Google API key, and runs on Android, iOS, and Web.
- Styles and tiles are a separate decision: CARTO, OpenFreeMap and Versatiles work without an account, commercial providers issue their own keys.

## Why developers look for a Capacitor Google Maps alternative

`@capacitor/google-maps` is a solid plugin, and for apps that need Google's map data it is the obvious choice. Three things regularly send teams looking elsewhere: the billing account, the Android rendering model, and the limits of the marker API.

### A billing account is required on every platform

The [official documentation](https://capacitorjs.com/docs/apis/google-maps){:target="_blank"} is direct about it: *"To use the Google Maps SDK on any platform, API keys associated with an account with billing enabled are required. This is required for all three platforms, Android, iOS, and Javascript."* The Maps SDK for Android [says the same](https://developers.google.com/maps/documentation/android-sdk/usage-and-billing){:target="_blank"}: *"you must enable billing on each of your projects and include an API key or OAuth token with all API or SDK requests."*

That means a card on file before the first map renders, plus three keys to create, restrict, and rotate. On [March 1, 2025](https://developers.google.com/maps/billing-and-pricing/march-2025){:target="_blank"}, Google also replaced the recurring $200 monthly credit with a free usage threshold per SKU. The [current caps](https://developers.google.com/maps/billing-and-pricing/pricing){:target="_blank"} are 10,000 free billable events per month for Essentials SKUs, 5,000 for Pro, and 1,000 for Enterprise.

To be fair, mobile usage of the Maps SDK is unlimited, so showing a map on a phone does not run up a bill. For most teams the deciding factor is the requirement itself rather than the invoice, especially on open-source apps and client work where nobody wants to own the billing account.

### On Android, the map renders behind the WebView

The docs also spell out the rendering model: *"On Android, the map is rendered beneath the entire webview, and uses this component to manage its positioning during scrolling events. This means that as the developer, you must ensure that the webview is transparent all the way through the layers to the very bottom."*

So you set `background: transparent` on `html`, `body`, `ion-content` and anything else covering the map, then undo it when the user leaves the page. Get it wrong and you see a blank rectangle, which is what happens in [this long-running issue](https://github.com/ionic-team/capacitor-google-maps/issues/69){:target="_blank"} and in the [dark mode report](https://github.com/ionic-team/capacitor-plugins/issues/1834){:target="_blank"} where Android's forced dark background paints over the map.

One honest caveat before you switch for this reason alone: the Capacitor MapLibre plugin renders behind the WebView too and needs the same transparent ancestors. Every plugin that draws a real native map view works this way. What differs is the vendor account, not the layering.

### Markers can be added and removed, but not updated

The marker API of `@capacitor/google-maps` consists of `addMarker`, `addMarkers`, `removeMarker` and `removeMarkers`. There is no update method, so moving a marker one meter means removing it, adding a new one, and storing the new ID. On a delivery screen that refreshes every few seconds, that flickers and drops any selection state attached to the marker. It is the core of the issue [Cannot get control of Markers, Circles etc](https://github.com/ionic-team/capacitor/issues/6575){:target="_blank"}.

## What is MapLibre?

MapLibre is an open-source map renderer, forked from Mapbox GL in 2020 after Mapbox moved to a proprietary license. It is BSD-licensed and renders any style that follows the [MapLibre Style Spec](https://maplibre.org/maplibre-style-spec/){:target="_blank"}, so it can point at many different tile providers without code changes.

The important split: the renderer is free and open source, the tiles and styles it draws are a separate decision you make yourself. The [Capacitor MapLibre plugin](../../sdks/capacitor/maplibre.md) wraps [MapLibre Native](https://github.com/maplibre/maplibre-native){:target="_blank"} on Android and iOS and MapLibre GL JS on the Web, giving you one TypeScript API across all three platforms. Setup is described in the [Installation](../../sdks/capacitor/maplibre.md#installation) section of the docs.

## @capacitor/google-maps vs. the Capacitor MapLibre plugin

Here is how the two compare on the features most map screens actually need.

|  | `@capacitor/google-maps` | Capacitor MapLibre plugin |
|---|---|---|
| Google API key + billing account | Required on Android, iOS, and Web | Not required |
| Platforms | Android, iOS, Web | Android, iOS, Web |
| Android/iOS rendering | Native view behind the WebView, transparent ancestors required | Native view behind the WebView, transparent ancestors required |
| Update a marker in place | ✗ remove and add again | ✓ `updateMarkerById(...)`, including rotation and animation |
| Marker dragging | ✓ all platforms | ✓ Android and Web only |
| Polylines | Add and remove | Add, remove, and `updatePolylineById(...)` |
| Polygons and circles | `addPolygons`, `addCircles` | GeoJSON source plus `LayerType.Fill` or `LayerType.Circle` |
| GeoJSON sources and layers | ✗ | ✓ `addGeoJsonSource(...)` and `addLayer(...)` |
| Style switching at runtime | Map types via `setMapType`, JSON styles set at creation | ✓ `setStyle(...)` with any Style Spec URL or JSON |
| Marker clustering | ✓ `enableClustering()` | ✗ |
| Satellite, terrain, traffic, indoor | ✓ built in | Depends on the tile provider you choose |
| Offline maps | ✗ | ✗ |
| Cost | Mobile map loads unlimited, Web under the Essentials free cap, billing account required either way | Plugin is free and open source, tiles depend on the provider |

Clustering and Google's own data layers are genuine wins for the official plugin, and the MapLibre plugin has no answer to either today. Markers, polylines and GeoJSON go the other way, because the MapLibre API was built around updating objects already on the map instead of replacing them.

## Can I use maps in a Capacitor app without a Google Maps API key?

Yes. The Capacitor MapLibre plugin has no vendor account of its own, so all it needs is a style URL that follows the MapLibre Style Spec. [CARTO basemaps](https://github.com/CartoDB/basemap-styles){:target="_blank"}, [OpenFreeMap](https://openfreemap.org/){:target="_blank"} and [Versatiles](https://versatiles.org/){:target="_blank"} serve styles you can point at without registering, while commercial providers such as [MapTiler](https://www.maptiler.com/){:target="_blank"} hand out their own API keys.

Treat the tile provider as its own decision with its own terms, because rate limits, attribution requirements and acceptable use differ per provider. The plugin's default is the MapLibre demo style, which exists for testing, so set `styleUrl` before you ship.

## Building a map with the Capacitor MapLibre plugin

The API is one flat TypeScript interface, and every method takes the `mapId` of the map it acts on. Here are the pieces of a typical map screen.

### Creating a map

Start with an empty element that defines where the map goes and how large it is.

```html
<div id="map"></div>
```

The element must stay empty and must not have a background, because the native map is drawn behind it.

```css
#map {
  background: transparent;
  height: 400px;
  width: 100%;
}
```

Then call [`createMap(...)`](../../sdks/capacitor/maplibre.md#createmap) with an ID for the map, the element to render into, and a style.

```typescript
import { MapLibre } from '@capawesome/capacitor-maplibre';

await MapLibre.createMap({
  center: { latitude: 48.137154, longitude: 11.576124 },
  elementId: 'map',
  mapId: 'my-map',
  styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
  zoom: 12,
});
```

The promise resolves once the style has loaded, so it is safe to add markers right after. On the Web, import `maplibre-gl/dist/maplibre-gl.css` once in your app. Call [`destroyMap(...)`](../../sdks/capacitor/maplibre.md#destroymap) when the user leaves the screen.

### Adding and updating markers

Markers are added with [`addMarker(...)`](../../sdks/capacitor/maplibre.md#addmarker) or [`addMarkers(...)`](../../sdks/capacitor/maplibre.md#addmarkers) and carry their own ID, so there is no handle to keep around.

```typescript
import { MapLibre, MarkerIconAnchor } from '@capawesome/capacitor-maplibre';

await MapLibre.addMarker({
  mapId: 'my-map',
  marker: {
    coordinates: { latitude: 48.137154, longitude: 11.576124 },
    iconAnchor: MarkerIconAnchor.Center,
    iconSize: { height: 32, width: 32 },
    iconUrl: 'https://example.com/marker.png',
    id: 'my-marker',
  },
});
```

This is where the two plugins differ most. [`updateMarkerById(...)`](../../sdks/capacitor/maplibre.md#updatemarkerbyid) changes only the properties you pass and can animate both the move and the rotation, which is what live vehicle tracking needs.

```typescript
await MapLibre.updateMarkerById({
  animate: true,
  animationDuration: 1000,
  coordinates: { latitude: 48.370545, longitude: 10.89779 },
  mapId: 'my-map',
  markerId: 'my-marker',
  rotation: 90,
});
```

Cleanup works per marker with [`removeMarkerById(...)`](../../sdks/capacitor/maplibre.md#removemarkerbyid) or in one call with [`removeAllMarkers(...)`](../../sdks/capacitor/maplibre.md#removeallmarkers).

### Drawing routes with polylines

Routes and recorded tracks are polylines, added with [`addPolyline(...)`](../../sdks/capacitor/maplibre.md#addpolyline) and styled with `color`, `width` and `opacity`.

```typescript
await MapLibre.addPolyline({
  mapId: 'my-map',
  polyline: {
    color: '#3887be',
    coordinates: [
      { latitude: 48.137154, longitude: 11.576124 },
      { latitude: 52.520008, longitude: 13.404954 },
    ],
    id: 'my-polyline',
    width: 5,
  },
});
```

As the route grows, [`updatePolylineById(...)`](../../sdks/capacitor/maplibre.md#updatepolylinebyid) replaces the coordinates in place instead of redrawing the line.

### Rendering GeoJSON data

This has no equivalent in `@capacitor/google-maps`. A GeoJSON source holds the data and a layer decides how it is painted, so one source can feed several layers.

```typescript
import { LayerType, MapLibre } from '@capawesome/capacitor-maplibre';

await MapLibre.addGeoJsonSource({
  mapId: 'my-map',
  sourceId: 'my-source',
  url: 'https://example.com/routes.geojson',
});
await MapLibre.addLayer({
  layerId: 'my-layer',
  mapId: 'my-map',
  paint: { lineColor: '#3887be', lineWidth: 4 },
  sourceId: 'my-source',
  type: LayerType.Line,
});
```

[`addGeoJsonSource(...)`](../../sdks/capacitor/maplibre.md#addgeojsonsource) takes either a `url` or inline `data`, and [`updateGeoJsonSourceById(...)`](../../sdks/capacitor/maplibre.md#updategeojsonsourcebyid) swaps the data of an existing source when live features arrive over a socket.

### Controlling the camera

[`setCamera(...)`](../../sdks/capacitor/maplibre.md#setcamera) moves the camera and only touches the properties you pass, including `bearing` and `pitch` for tilted views.

```typescript
await MapLibre.setCamera({
  animate: true,
  bearing: 30,
  center: { latitude: 52.520008, longitude: 13.404954 },
  mapId: 'my-map',
  pitch: 45,
  zoom: 11,
});
```

For search results, [`fitBounds(...)`](../../sdks/capacitor/maplibre.md#fitbounds) frames a bounding box with optional `padding`, and [`getCamera(...)`](../../sdks/capacitor/maplibre.md#getcamera) reads the position back.

### Showing the location of the user

Location permissions are handled by the plugin, so you check first and request only when needed.

```typescript
import { MapLibre, UserTrackingMode } from '@capawesome/capacitor-maplibre';

let status = await MapLibre.checkPermissions();
if (status.location === 'prompt') {
  status = await MapLibre.requestPermissions();
}
if (status.location === 'granted') {
  await MapLibre.enableUserLocation({
    mapId: 'my-map',
    trackingMode: UserTrackingMode.Follow,
  });
}
```

`UserTrackingMode.FollowWithCourse` points the camera in the direction the user moves and `FollowWithHeading` follows the compass of the device, the two modes navigation screens usually want.

### Reacting to map events

Events are regular Capacitor listeners, and each carries the `mapId` so a screen with several maps can tell them apart.

```typescript
await MapLibre.addListener('mapClick', event => {
  console.log('Map clicked:', event.coordinates);
});
await MapLibre.addListener('markerClick', event => {
  console.log('Marker clicked:', event.markerId);
});
await MapLibre.addListener('cameraIdle', event => {
  console.log('Camera idle:', event.camera);
});
```

`cameraMoveStarted` reports through `CameraMoveReason` whether the movement came from a gesture or from your own code, which decides when to reload data for the visible area. Drag events and the `draggable` property work on Android and Web only, because markers are rendered as symbol layers on iOS.

### Switching styles at runtime

Since a style is just a URL, dark mode is a style swap rather than a map option.

```typescript
await MapLibre.setStyle({
  mapId: 'my-map',
  url: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
});
```

Keep in mind that [`setStyle(...)`](../../sdks/capacitor/maplibre.md#setstyle) loads a completely new style, so markers, polylines, sources and layers have to be added again afterwards. Running several maps side by side works the same way, since every method is addressed by `mapId`.

## When Google Maps is still the better choice

MapLibre gives you a renderer. Google gives you a renderer plus one of the best map datasets in the world. Pick `@capacitor/google-maps` when you need Street View, POI and Places data, live traffic, indoor floor plans, or satellite and terrain imagery under a single account. Built-in clustering through `enableClustering()` is another concrete advantage, and if your app already calls the Places or Directions APIs, staying inside one platform and one key is less work. If the map itself is the product, Google's data is hard to replace.

## FAQ

### Is the Capacitor MapLibre plugin free?

Yes. The plugin is free and open source, and MapLibre itself is BSD-licensed. The styles and tiles it renders come from a provider you choose, and those carry their own terms and attribution requirements.

### Do I need a Google Cloud billing account?

No. The plugin never talks to Google Maps Platform, so there is no project, no API key and no billing account to set up.

### Does the plugin work on the web?

Yes, through MapLibre GL JS, which ships as a dependency of the plugin. Import `maplibre-gl/dist/maplibre-gl.css` once, otherwise the canvas and the markers end up in the wrong place.

### Does it support offline maps?

No. Styles, fonts, sprites and tiles are loaded over the network, and offline tile management is not part of the plugin today.

### Can I migrate from @capacitor/google-maps without rewriting my UI?

Mostly. The concepts map one to one (map, camera, markers, polylines, events), so your screens and state usually survive. What changes are the method names and the call shape: every MapLibre method is static and takes a `mapId` instead of hanging off a map instance.

## Stay in the loop

New plugins and features land regularly, and the newsletter is the fastest way to hear about them.

[Subscribe to the Capawesome Newsletter](https://capawesome.io/newsletter/){ .md-button .md-button--primary }

## Conclusion

The choice comes down to renderer versus data. If your map is a canvas for your own points, routes and areas, the [Capacitor MapLibre plugin](../../sdks/capacitor/maplibre.md) gets you there without a Google Cloud project, and its update methods fit live data better than an add-and-remove API. If you need Street View, Places, traffic or clustering, `@capacitor/google-maps` remains the right tool and the billing account is part of the deal.

For the sensors that pair well with a map screen, [The Complete Guide to Capacitor Device Sensors](./capacitor-device-sensors-guide.md) is a good next read. Questions are welcome in the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}, and the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} keeps you posted on what ships next.
