Native ARKit and ARCore sessions for Expo and React Native, powered by Nitro Modules.
Works with Expo development builds • Documentation • Issues
munim-xr gives one typed React Native API to world-tracked augmented reality on iOS and Android. Its Nitro Hybrid View renders the native camera feed and exposes session lifecycle, camera pose, plane detection, hit testing, anchors, light estimates, optional depth, and snapshots.
The package uses ARKit on iOS and ARCore on Android. It contains native code, requires React Native's New Architecture, and does not run in Expo Go.
- Native
XRViewbacked byARSCNViewon iOS and an ARCoreGLSurfaceViewon Android - Horizontal and vertical plane detection with add, update, and remove callbacks
- Throttled frame callbacks with camera pose, tracking state, and ambient light
- Normalized screen-space hit testing and persistent native anchors
- Optional scene depth when supported by the device
- PNG snapshots written to a temporary or cache path
- ARKit/ARCore availability and install helpers
- Expo config plugin for camera permission and optional/required AR capabilities
- Fully typed TypeScript API generated through Nitro Modules
| Capability | iOS | Android |
|---|---|---|
| World tracking | ARKit | ARCore |
| Camera background | ARSCNView |
OpenGL ES external texture |
| Horizontal/vertical planes | Yes | Yes |
| Hit testing | Plane raycasts | Plane, depth, point, and estimated hits |
| Native anchors | Yes | Yes |
| Ambient light estimate | Yes | Yes |
| Optional depth | Scene Depth when supported | Automatic Depth when supported |
| Snapshot | PNG temporary file | PNG cache file |
| Expo Go | No | No |
npm install munim-xr react-native-nitro-modulesWith Expo:
npx expo install munim-xr react-native-nitro-modulesAdd the config plugin when your Expo project manages plugins explicitly:
{
"expo": {
"plugins": [
[
"munim-xr",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to use the camera for augmented reality.",
"arRequired": false
}
]
]
}
}arRequired: false keeps the app installable on devices without AR support. Set it to true only when the rest of your app cannot work without AR.
Create a native build after installation:
npx expo prebuild
npx expo run:ios
# or
npx expo run:androidFor bare React Native on iOS, run pod install in the ios directory.
- React Native 0.78 or newer with the New Architecture enabled
react-native-nitro-modules0.36.5 or newer- iOS 15.1 or newer on an ARKit-capable device
- Android API 24 or newer with Google Play Services for AR
- A physical device for normal runtime testing
Nitro callbacks must be wrapped with callback(). Capture the Hybrid View reference through hybridRef to call native methods.
import { useMemo, useRef } from 'react'
import { StyleSheet } from 'react-native'
import { callback } from 'react-native-nitro-modules'
import {
checkAvailability,
requestCameraPermission,
requestInstall,
XRView,
type XRViewRef,
} from 'munim-xr'
export function ARScene() {
const xrRef = useRef<XRViewRef | null>(null)
const hybridRef = useMemo(
() => callback((ref: XRViewRef) => (xrRef.current = ref)),
[]
)
const onReady = useMemo(
() => callback(() => console.log('XR ready')),
[]
)
async function start() {
if (!(await requestCameraPermission())) return
const availability = await checkAvailability()
if (availability === 'not-installed' || availability === 'update-required') {
if (!(await requestInstall())) return
}
if (availability === 'unsupported') return
await xrRef.current?.start()
}
return (
<XRView
depthEnabled={false}
frameCallbackFps={15}
hybridRef={hybridRef}
lightEstimationEnabled
onReady={onReady}
planeDetection="both"
style={StyleSheet.absoluteFill}
/>
)
}Call hitTest(x, y) with normalized coordinates from 0 to 1. An anchor can be created directly from a hit pose:
const hits = await xrRef.current?.hitTest(0.5, 0.5)
if (hits?.[0]) {
const anchor = await xrRef.current?.createAnchor(hits[0].pose)
console.log(anchor?.id)
}isSupported(): boolean— returns the native runtime's immediate support resultcheckAvailability(): Promise<XRAvailability>— reportssupported,unsupported,not-installed,update-required, orunknownrequestInstall(): Promise<boolean>— requests ARCore installation/update on Android; returns the current ARKit support result on iOSrequestCameraPermission(): Promise<boolean>— requests Android camera permission; iOS prompts when the session startsgetXRPlatform(): string— returnsarkitorarcoregetXRSDKVersion(): string— returns the native XR SDK description
planeDetection:none,horizontal,vertical, orbothdepthEnabled: enables supported native depth semanticslightEstimationEnabled: enables ambient light estimatesframeCallbackFps: limits JavaScript frame callback frequency; use0to disableonReady,onFrame,onTrackingStateChange,onPlaneDetected,onPlaneUpdated,onPlaneRemoved,onError
start()andpause()control the native sessionreset()restarts tracking and removes existing native tracking statehitTest(normalizedX, normalizedY)returns orderedXRHitResultvaluescreateAnchor(pose),removeAnchor(id), andgetAnchors()manage native anchorsgetCameraPose()returns the latest camera pose when availablecaptureSnapshot()returns a local PNG path
- AR support varies by device. Check availability before exposing an AR-only flow.
- ARCore installation can take the user out of the app. If
requestInstall()returnsfalse, ask them to finish installation and try again. - A pose matrix contains 16 column-major values in native AR coordinates, measured in meters.
- Frame callbacks cross the native/JavaScript boundary. Keep
frameCallbackFpsas low as your UI permits. - A snapshot path points into temporary app storage; move the file if it must persist.
- The iOS Simulator can compile the module but does not provide a normal world-tracking ARKit session.
npm install
npm run codegen
npm run checkGenerated Nitro bindings live under packages/munim-xr/nitrogen/generated and are committed. Native implementation files live outside that generated directory.
Apache-2.0. ARKit, ARCore, React Native, Expo, and Nitro Modules retain their respective licenses and terms.