Direct integration with native platform modules through JSI (JavaScript Interface) with automatic fallback to bridge proxy for backwards compatibility.
Functions for importing and accessing native modules with error handling and fallback mechanisms.
/**
* Imports the native module registered with given name
* Tries JSI host object first, then falls back to bridge proxy
* @param moduleName - Name of the requested native module
* @returns Object representing the native module
* @throws Error when there is no native module with given name
*/
function requireNativeModule<ModuleType = any>(moduleName: string): ModuleType;
/**
* Imports the native module registered with given name
* Same as requireNativeModule but returns null instead of throwing
* @param moduleName - Name of the requested native module
* @returns Object representing the native module or null if not found
*/
function requireOptionalNativeModule<ModuleType = any>(
moduleName: string
): ModuleType | null;Usage Examples:
import { requireNativeModule, requireOptionalNativeModule } from "expo-modules-core";
// Required module - throws if not found
try {
const locationModule = requireNativeModule("ExpoLocation");
const position = await locationModule.getCurrentPositionAsync();
console.log("Current position:", position);
} catch (error) {
console.error("Location module not available:", error.message);
}
// Optional module - returns null if not found
const cameraModule = requireOptionalNativeModule("ExpoCamera");
if (cameraModule) {
const permissions = await cameraModule.requestPermissionsAsync();
console.log("Camera permissions:", permissions);
} else {
console.log("Camera module not available");
}
// Type-safe module loading
interface LocationModule {
getCurrentPositionAsync(): Promise<{
coords: { latitude: number; longitude: number };
timestamp: number;
}>;
requestPermissionsAsync(): Promise<{ granted: boolean }>;
}
const typedLocationModule = requireNativeModule<LocationModule>("ExpoLocation");
const position = await typedLocationModule.getCurrentPositionAsync();
// position is fully typedBase class for all native modules that extends EventEmitter with additional native module capabilities.
/**
* A class for all native modules. Extends the EventEmitter class.
* Provides event handling capabilities and native view prototypes.
*/
class NativeModule<TEventsMap extends EventsMap = Record<never, never>>
extends EventEmitter<TEventsMap> {
/**
* Prototypes of native components exported by the module
* Used for binding native view functions to JavaScript
* @private
*/
ViewPrototypes?: { [viewName: string]: object };
/**
* Dynamic properties for module-specific functionality
* Allows access to native methods and properties
*/
[key: string]: any;
}Usage Examples:
import { NativeModule, requireNativeModule } from "expo-modules-core";
// Access native module as NativeModule instance
const audioModule = requireNativeModule("ExpoAudio") as NativeModule<{
playbackStatusChanged: (status: PlaybackStatus) => void;
audioInterruption: (interruption: AudioInterruption) => void;
}>;
// Use as event emitter
const subscription = audioModule.addListener("playbackStatusChanged", (status) => {
console.log("Playback status:", status);
});
// Access native methods
const sound = await audioModule.createSound("audio.mp3");
await audioModule.playAsync(sound);
// Check for view prototypes (used internally by view managers)
if (audioModule.ViewPrototypes) {
console.log("Available view prototypes:", Object.keys(audioModule.ViewPrototypes));
}
// Clean up
subscription.remove();Register web-based implementations of native modules for cross-platform compatibility.
/**
* Registers a web module implementation
* @param moduleImplementation - Class extending NativeModule to register
* @param moduleName - Name to register the module under (optional, uses class name)
* @returns Singleton instance of the registered class
*/
function registerWebModule<
EventsMap extends Record<never, never>,
ModuleType extends typeof NativeModule<EventsMap>
>(moduleImplementation: ModuleType, moduleName?: string): ModuleType;Usage Examples:
import { NativeModule, registerWebModule } from "expo-modules-core";
// Define events for your module
type StorageEvents = {
itemChanged: (key: string, value: string) => void;
cleared: () => void;
};
// Create web implementation
class WebStorageModule extends NativeModule<StorageEvents> {
private storage = new Map<string, string>();
async setItem(key: string, value: string): Promise<void> {
this.storage.set(key, value);
this.emit("itemChanged", key, value);
}
async getItem(key: string): Promise<string | null> {
return this.storage.get(key) || null;
}
async removeItem(key: string): Promise<void> {
this.storage.delete(key);
}
async clear(): Promise<void> {
this.storage.clear();
this.emit("cleared");
}
}
// Register the web module
const webStorage = registerWebModule(WebStorageModule, "ExpoStorage");
// The module is now available via requireNativeModule
const storageModule = requireNativeModule("ExpoStorage");
await storageModule.setItem("key", "value");
const value = await storageModule.getItem("key");The module loading system uses a priority chain to find native modules:
// Internal loading logic (for reference)
function loadModule(moduleName: string) {
return (
globalThis.expo?.modules?.[moduleName] ?? // JSI (preferred)
NativeModulesProxy[moduleName] ?? // Bridge proxy
createTurboModuleToExpoProxy( // TurboModule fallback
TurboModuleRegistry.get(moduleName),
moduleName
) ??
null // Not found
);
}Usage Examples:
import { requireOptionalNativeModule } from "expo-modules-core";
// Check module availability and loading method
const module = requireOptionalNativeModule("MyModule");
if (module) {
// Module loaded successfully via one of the fallback methods
console.log("Module available");
// Check if it's a JSI module (has direct native binding)
const isJSI = globalThis.expo?.modules?.["MyModule"] != null;
console.log("Using JSI:", isJSI);
} else {
console.log("Module not available on this platform");
}/**
* Base type for events map used by NativeModule
*/
type EventsMap = Record<string, (...args: any[]) => void>;
/**
* Type for legacy proxy native modules
*/
interface ProxyNativeModule {
addListener?(eventName: string): void;
removeListeners?(count: number): void;
[key: string]: any;
}