Sometimes you need access to specific native features that aren't available in existing packages, or you want to implement a custom integration for your project. Native modules allow you to bridge native code (Swift/Kotlin) with JavaScript.

Why Expo Modules API?

While there are other solutions like Turbo Modules (complex to implement, requires C++) and Nitro Modules (smaller community, still evolving), we'll focus on Expo Modules because it offers:

  • Flexibility: Works with both Expo and bare React Native projects
  • Well Maintained: Actively developed by the Expo team
  • Well Documented: Comprehensive guides and API reference
  • Future-Proof: Continuously improved and supported for years to come

API Design Philosophy

Before diving into the technical details, it's important to understand a key principle: even though you'll be writing native code in Swift and Kotlin, your primary interface will be through TypeScript. This means you need to think carefully about your API design from the start, defining consistent method names, parameters, and return types that work seamlessly across both platforms. The native implementations may differ under the hood, but the JavaScript API should feel unified and intuitive. This TypeScript-first approach ensures your module is easy to use and maintain, regardless of which platform it's running on.

Platform-Specific Modules

While we emphasize creating a unified API across platforms, it's perfectly acceptable to build native modules for a single platform when needed. For example, if you're integrating with iOS-specific frameworks like HealthKit or Android-specific features like home screen widgets, you only need to implement the relevant platform. You can use platform-specific extensions (.ios.ts and .android.ts) to handle the unsupported platform gracefully, whether that means showing a message, disabling a feature, or providing an alternative experience.

I recommend looking at the expo-symbols package as an example of how to handle platform-specific extensions.

Expo Modules API Basics

When creating a native module with Expo, you'll write code in both Swift (iOS) and Kotlin (Android). Here are the essential building blocks:

Module Name

Define what your module will be called in JavaScript:

// Swift (iOS)
public class MyModule: Module {
  public func definition() -> ModuleDefinition {
    Name("MyModule")
  }
}
// Kotlin (Android)
class MyModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("MyModule")
  }
}

Constants

Export constant values to JavaScript:

// Swift
Constant("PI") {
  Double.pi
}
// Kotlin
Constant("PI") {
  Math.PI
}

Functions

Create functions that can be called from JavaScript:

// Swift
Function("hello") { (name: String) in
  return "Hello \(name)!"
}
// Kotlin
Function("hello") { name: String ->
  return@Function "Hello $name!"
}

Using in JavaScript

import { requireNativeModule } from "expo-modules-core";
 
const MyModule = requireNativeModule("MyModule");
 
console.log(MyModule.PI); // 3.14159...
console.log(MyModule.hello("Beto")); // "Hello Beto!"

Learn More

This is just the beginning! Check out the complete Expo Modules API Reference to learn about async functions, events, view components, and more advanced features.

In the following lessons, we'll build a complete native module from scratch.

Is this lesson useful?