Introduction
React Native empowers developers to build cross-platform mobile apps using JavaScript and declarative UI—but sometimes you need access to a platform-specific feature or optimized native code. That’s where custom native modules come in. By writing a thin bridge between native (Java/Objective-C/Swift) and JavaScript, you can leverage device APIs or third-party SDKs not exposed by React Native out of the box. In this guide, you’ll learn why and when to build native modules, how to set up your environment, and step-by-step instructions for both Android and iOS, plus tips on bridging, error handling, and best practices for a robust integration.

Why Create Custom Native Modules?
- Access Platform APIs
Some APIs—like advanced biometrics, background services, or low-level sensors—aren’t yet supported by React Native’s core or community packages. - Performance-Critical Code
Offload heavy computation (e.g., image processing, cryptography) to native code for speed and memory efficiency. - Third-Party SDK Integration
Many enterprise or analytics SDKs ship only with native bindings; modules let you wrap them for JS usage. - Incremental Migration
In a brownfield app, you can gradually replace parts of a native codebase with React Native while still calling existing modules.
Setting Up Your Environment
- React Native Project bashCopyEdit
npx react-native init NativeModuleDemo cd NativeModuleDemo
- Android Prerequisites
- Android Studio with SDK platforms and NDK
JAVA_HOME
and Android SDK paths configured
- iOS Prerequisites
- Xcode ≥ 12
- CocoaPods installed (
gem install cocoapods
)
- Directory Structure cssCopyEdit
NativeModuleDemo/ ├── android/ ├── ios/ └── src/ └── NativeModules/
Android: Creating a Native Module
1. Write the Java/Kotlin Module
In android/app/src/main/java/com/nativemoduledemo/ExampleModule.kt
:
kotlinCopyEditpackage com.nativemoduledemo
import com.facebook.react.bridge.*
class ExampleModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "ExampleModule"
@ReactMethod
fun multiply(a: Int, b: Int, promise: Promise) {
try {
val result = a * b
promise.resolve(result)
} catch (e: Exception) {
promise.reject("ERROR", e)
}
}
}
2. Register the Module in a Package
In ExamplePackage.kt
:

kotlinCopyEditpackage com.nativemoduledemo
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class ExamplePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(ExampleModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext) = emptyList<ViewManager>()
}
3. Add the Package to MainApplication
In android/app/src/main/java/com/nativemoduledemo/MainApplication.java
:
javaCopyEditimport com.nativemoduledemo.ExamplePackage; // add import
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ExamplePackage() // register here
);
}
iOS: Creating a Native Module
1. Write the Objective-C/Swift Module
Objective-C (ExampleModule.m/.h)
ios/NativeModuleDemo/ExampleModule.h
:
objcCopyEdit#import <React/RCTBridgeModule.h>
@interface ExampleModule : NSObject <RCTBridgeModule>
@end
ios/NativeModuleDemo/ExampleModule.m
:
objcCopyEdit#import "ExampleModule.h"
@implementation ExampleModule
RCT_EXPORT_MODULE();
RCT_REMAP_METHOD(multiply,
multiplyWithA:(NSInteger)a b:(NSInteger)b resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
@try {
NSInteger result = a * b;
resolve(@(result));
} @catch (NSError *error) {
reject(@"ERROR", @"Error multiplying", error);
}
}
@end
Swift (Optional)
Enable bridging by adding SwiftExample.swift
and updating the bridging header.
2. Register the Module
CocoaPods auto-linking in RN ≥ 0.60 will detect RCTBridgeModule
implementations. Otherwise, add to AppDelegate.m
:

objcCopyEdit#import <React/RCTBridgeModule.h>
Bridging to JavaScript
In src/NativeModules/index.js
:
jsCopyEditimport { NativeModules } from 'react-native';
const { ExampleModule } = NativeModules;
export default {
multiply: (a, b) => ExampleModule.multiply(a, b),
};
Usage in your React components:
jsxCopyEditimport React, { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
import ExampleModule from './src/NativeModules';
export default function App() {
const [result, setResult] = useState(null);
const handleMultiply = async () => {
try {
const value = await ExampleModule.multiply(6, 7);
setResult(value);
} catch (err) {
console.error(err);
}
};
return (
<View style={{padding: 20}}>
<Button title="Multiply 6 × 7" onPress={handleMultiply} />
{result !== null && <Text>Result: {result}</Text>}
</View>
);
}
Testing and Error Handling
- Unit Tests: Write Jest mocks for native modules using
jest.mock('react-native', ...)
. - Runtime Checks: Validate module existence and method availability before calling.
- Promise Handling: Always handle rejections to prevent unhandled promise errors.

Best Practices
- Keep Module Surface Small: Export focused methods; avoid large object graphs across the bridge.
- Batch Calls: Combine multiple native invocations into one call if possible to minimize bridge overhead.
- Threading: Perform heavy work on background threads in native code to avoid JS blocking.
- Documentation: Document native method signatures and promise behavior for JS consumers.
Conclusion
Custom native modules let you fill gaps in React Native’s cross-platform APIs, integrate third-party SDKs, and optimize performance-critical code. By following the patterns above—setting up packages, writing bridge code, and consuming from JavaScript—you can build maintainable, performant modules for both Android and iOS. Start with small, focused modules and evolve your codebase as your app’s requirements grow.