Expo module for turn-by-turn navigation on iOS and Android using Mapbox Navigation SDK v3 (iOS) / Android. Single MapboxNavigation component, Expo config plugin for credentials, no vendored binaries. Minimal alternative to existing community wrappers.
Alpha — iOS is working in our builds; Android is not yet working (dependency resolution / Mapbox Maven). We need help to get Android over the line. APIs may change. Contributions welcome. Open an issue or reach out.
Package summary: Expo config plugin + native module; Mapbox Navigation SDK v3; iOS (SPM) and Android (Maven, drop-in NavigationView); turn-by-turn driving/walking/cycling; requires Expo ≥51, React Native ≥0.74, Mapbox public + secret tokens; alpha stage; TypeScript API via MapboxNavigation component.
- Prerequisites
- Installation
- Usage
- API
- Architecture
- Why this exists
- Comparison
- Status
- Integration testing
- Contributing
- License
- Sponsors
- Mapbox account with Navigation SDK access
Mapbox requires two tokens (create both in your Mapbox tokens page):
| Token | Prefix | Purpose |
|---|---|---|
| Public (access) token | pk.xxx |
Used by the app at runtime: map tiles, Directions API, voice, etc. Set as mapboxAccessToken in the plugin. |
| Secret (downloads) token | sk.xxx |
Used only at build time: Gradle (Android) and SPM (iOS) use it to download the Navigation SDK from Mapbox. Must have Downloads:Read scope. Not used by the app at runtime. |
The same secret token is used for both platforms. For EAS Build, one EAS secret (e.g. MAPBOX_DOWNLOADS_TOKEN or MAPBOX_SECRET_TOKEN) can back both: iOS needs it in ~/.netrc for SPM; Android needs it in gradle.properties (the plugin writes it from mapboxSecretToken) and, when using centralized repo resolution, as env var MAPBOX_DOWNLOADS_TOKEN.
-
iOS: Add the secret token to
~/.netrcso SPM can download the SDK (local dev and EAS Build):machine api.mapbox.com login mapbox password YOUR_SECRET_TOKEN -
Android: The plugin writes
mapboxSecretTokentoandroid/gradle.propertiesasMAPBOX_DOWNLOADS_TOKENso Maven can download the SDK. For EAS Build, also setMAPBOX_DOWNLOADS_TOKENas an EAS secret so the build can authenticate. -
Android (local builds): Gradle must run on JVM 17 or greater. If you see “Executing Gradle on JVM versions 16 and lower has been deprecated”, set
JAVA_HOMEto a JDK 17+ installation (e.g.export JAVA_HOME=$(/usr/libexec/java_home -v 17)on macOS).
npx expo install @baeckerherz/expo-mapbox-navigationAdd the plugin in app.config.ts (or app.json). Prefer environment variables for tokens so you don’t commit secrets:
plugins: [
["@baeckerherz/expo-mapbox-navigation/plugin", {
mapboxAccessToken: process.env.MAPBOX_ACCESS_TOKEN,
mapboxSecretToken: process.env.MAPBOX_SECRET_TOKEN,
navigationSdkVersion: "3.5.0",
}],
]Rebuild native projects:
npx expo prebuild --clean
npx expo run:ios
npx expo run:androidimport { MapboxNavigation } from '@baeckerherz/expo-mapbox-navigation';
<MapboxNavigation
coordinates={[
{ latitude: 47.2692, longitude: 11.4041 },
{ latitude: 48.2082, longitude: 16.3738 },
]}
locale="de"
onRouteProgressChanged={(e) => {
console.log(e.nativeEvent.distanceRemaining);
}}
onCancelNavigation={() => navigation.goBack()}
onFinalDestinationArrival={() => console.log('Arrived!')}
style={{ flex: 1 }}
/>Route
| Prop | Type | Description |
|---|---|---|
coordinates |
Array<{ latitude, longitude }> |
Route waypoints (min 2). First = origin, last = destination. |
waypointIndices |
number[] |
Indices in coordinates that are full waypoints (with arrival notification). Others are via-points. Must include first and last. |
routeProfile |
string |
Routing profile. iOS: "mapbox/driving-traffic" (default), "mapbox/driving", "mapbox/walking", "mapbox/cycling". Android: omit "mapbox/" prefix. |
Localization
| Prop | Type | Description |
|---|---|---|
locale |
string |
BCP 47 language for voice and UI (e.g. "de", "en-US"). Default: device locale. |
mute |
boolean |
Mute voice guidance. Default: false. |
Appearance
| Prop | Type | Description |
|---|---|---|
mapStyle |
string |
Mapbox style URL. Example: "mapbox://styles/mapbox/navigation-night-v1". |
themeMode |
"day" | "night" | "auto" |
Day (default), night, or auto by time. |
accentColor |
string |
Primary accent (hex). Example: "#007AFF". |
routeColor |
string |
Route line color (hex). Overrides accentColor for the line. |
bannerBackgroundColor |
string |
Instruction banner background (hex). |
bannerTextColor |
string |
Instruction banner text (hex). |
| Event | Payload | Description |
|---|---|---|
onRouteProgressChanged |
{ distanceRemaining, durationRemaining, distanceTraveled, fractionTraveled } |
Progress along the route. |
onCancelNavigation |
— | User cancelled. |
onWaypointArrival |
{ waypointIndex } |
Reached an intermediate waypoint. |
onFinalDestinationArrival |
— | Reached final destination. |
onRouteChanged |
— | Route recalculated (reroute). |
onUserOffRoute |
— | User left the route. |
onError |
{ message } |
Navigation error. |
iOS — SPM via config plugin
Mapbox Navigation SDK v3 is SPM-only. The Expo config plugin injects SPM package references into the Xcode project at prebuild. Version bumps are a single string change; no vendored xcframeworks.
Android — Drop-in NavigationView
We wrap the Nav SDK v3 NavigationView directly instead of rebuilding the UI.
Both — Expo Module API
expo-modules-core for native bridging: Fabric/New Architecture, type-safe props and events, and compatibility with Expo and bare React Native.
Existing wrappers have major drawbacks:
- @badatgil/expo-mapbox-navigation: Vendored
.xcframeworkon iOS (fragile; manual rebuild per SDK update). Android uses ~30 custom Kotlin components (~1100 LOC) instead of Mapbox’s drop-in view. - @homee/react-native-mapbox-navigation: Unmaintained (no activity since 2022), Nav SDK v2.1.1, no Expo, crashes on Android 13+.
| This module | @badatgil/expo-mapbox-navigation | @homee/react-native-mapbox-navigation | |
|---|---|---|---|
| iOS | SPM via config plugin | Vendored .xcframeworks | CocoaPods, Nav SDK v2 |
| Android | Drop-in NavigationView | Custom UI (~1100 LOC) | Custom UI (~500 LOC) |
| Nav SDK | v3 | v3 | v2 (legacy) |
| Expo Module API | Yes (Fabric-ready) | Yes | No |
| Multi-waypoint | Yes | Yes | No |
| Maintenance | Active | Semi-active | Abandoned |
Alpha. Platform status:
| Platform | Status | Notes |
|---|---|---|
| iOS | Working (Alpha) | SPM via config plugin; tested in our project builds. |
| Android | Not yet working | Mapbox Maven / dependency resolution issues; help wanted to fix. |
Goals: reliable SPM injection (iOS), drop-in NavigationView/NavigationViewController, event bridging. We want more feedback and testing. For prerelease we may publish under the alpha npm tag.
Help wanted — especially to get Android builds working (Gradle, Mapbox repo auth, or switching to ui-components if the drop-in artifact is unavailable).
Known risks
- Android: Build and dependency resolution need community input.
- Licensing: Mapbox Navigation SDK requires a commercial Mapbox license; this wrapper does not change that.
Use this flow to validate your changes before releasing: run the full integration from the repo root. It builds the example app with the local package, runs release checks, builds Android and iOS (with JVM 17+ for Gradle), and optionally runs Maestro E2E.
# Set Mapbox tokens so prebuild can fetch the SDK
export MAPBOX_PUBLIC_TOKEN="pk.xxx"
export MAPBOX_SECRET_TOKEN="sk.xxx" # or MAPBOX_DOWNLOADS_TOKEN for Android
yarn test:integration # build both Android and iOS, then E2E
yarn test:integration --ios # build only iOS, then E2E
yarn test:integration --android # build only Android, then E2E
yarn test:integration --e2e-only # skip build; run only Maestro E2E (app must already be on simulator)The integration script:
- Runs release checks (plugin build + static native checks for single provider / route clear).
- Links the local package in
example/and installs dependencies. - Prebuilds the example app (generates
ios/andandroid/). - Builds Android (
./gradlew assembleDebug). - Builds iOS (
xcodebuildfor Simulator). - If Maestro is available, boots an iOS simulator (if none is running), installs/launches the example app, then runs the E2E flow (start navigation → tap “Next stop” to test route refresh).
The script installs Maestro and Java 17 if missing. For E2E it starts the simulator and the app automatically. The example app includes a “Next stop” button that changes the route coordinates; the Maestro flow verifies this doesn’t crash (single provider on iOS, clear+callback on Android). To run only E2E (app must already be installed): yarn test:integration --e2e-only.
We welcome contributors and maintainers. If you work on Expo native modules, Mapbox SDKs, or React Native tooling, we’d love your help. If you or your company use this package, we’d love to hear from you (issues, discussions, or partner@baeckerherz.at) — it helps us prioritize and justify ongoing work.
Project layout: src/ (TypeScript API), ios/ (Swift + podspec), android/ (Kotlin + build.gradle), plugin/ (Expo config plugins), example/ (test app).
Run the example:
git clone https://github.com/baeckerherz/expo-mapbox-navigation.git
cd expo-mapbox-navigation && yarn install
cd example && yarn install
npx expo prebuild --clean
npx expo run:ios --device # or: npx expo run:androidThe example navigates from your location to Innsbruck Hauptbahnhof with German voice guidance.
Open an issue or submit a PR to get started.
We’d like to list teams and projects using this package (with your permission). If you’re using it, open an issue or email partner@baeckerherz.at.
Bäckerherz — Founding sponsor. They build and use this module; the project exists thanks to their investment in open-source Expo tooling.
TourenFlow — Intelligent tour planning and route optimization.
To support the project or work with us: partner@baeckerherz.at.