In React Native, what is a Turbo Native Module spec, and what does Codegen generate from it?
answer
- a typed contract written first
- TypeScript or Flow interface named Spec
- Codegen runs during the native build
- C++ glue plus Java and Objective-C++ interfaces
- native side implements what was generated
basics
~20 sA Turbo Native Module spec is a TypeScript or Flow file, such as NativeBatteryHealth.ts, whose Spec interface declares the module's methods. At build time Codegen turns it into C++ glue, a Java spec class and an Objective-C++ protocol for native code to implement.
solid answer
~40 sA **spec** is the typed contract for a Turbo Native Module, written before any native code. It lives in a file whose name starts with `Native` (for example `NativeBatteryHealth.ts`), declares `export interface Spec extends TurboModule` with the module's methods, and default-exports `TurboModuleRegistry.getEnforcing<Spec>('NativeBatteryHealth')` so JavaScript gets a typed module object. When the app builds, **Codegen** reads the spec and generates C++ glue that connects the JavaScript runtime to native code, an abstract Java class such as `NativeBatteryHealthSpec` for Android, and an Objective-C++ protocol and glue for iOS. You then write native classes that implement those generated interfaces. The spec is the single source of truth: change a method there and both platforms' generated interfaces change with it.
code
typescript · 10 linesimport type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
getBatteryLevel(): number;
isCharging(): boolean;
getCycleCount(): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeBatteryHealth');go deeper
Remember that a Turbo Native Module starts with a typed TypeScript or Flow spec, and that Codegen turns it into the native interfaces you implement.
Describe the spec's parts: Native-prefixed file, interface named Spec extending TurboModule, typed TurboModuleRegistry default export, and what Codegen generates on each platform.
Be precise about the guarantees: Android fails to compile on a missing method, iOS flags protocol gaps, TypeScript checks call sites only at type-check time, and nothing constrains behaviour.
Use the spec as the review boundary for native work: agreeing the interface first lets JavaScript and platform engineers work in parallel against one contract.
## What problem the spec solves A **native module** lets JavaScript call platform code that React Native does not expose: reading a battery's health on a managed device, talking to a hardware SDK, using a platform API. Under the old architecture a module was written natively first and JavaScript simply trusted whatever names and argument shapes it exposed, so a typo or a changed signature surfaced as a runtime failure. The New Architecture's **Turbo Native Modules** flip the order. You first write a **typed specification** in TypeScript or Flow, and React Native's **Codegen** tool turns it into native interfaces. React Native's docs describe a Turbo Native Module as three things together: the spec, the native code you write, and the interfaces generated from the spec. ## What a spec looks like For a device-management app that reports battery health, the spec might be `specs/NativeBatteryHealth.ts`: - the file name starts with **`Native`**, which is how Codegen recognises module specs; - it imports the `TurboModule` type and `TurboModuleRegistry` from `react-native`; - it declares **`export interface Spec extends TurboModule`** listing each method with its parameter and return types; - it **default-exports** a typed lookup, `TurboModuleRegistry.getEnforcing<Spec>('NativeBatteryHealth')`, so app code imports a ready, typed module object. App code then does `import NativeBatteryHealth from './specs/NativeBatteryHealth'` and calls its methods with full editor completion and type checking. ## What Codegen generates Codegen runs as part of the native build and generates code on both sides of the boundary: | Output | Platform | What you do with it | |---|---|---| | C++ glue code | Both | Nothing; it connects the JavaScript runtime to the module | | Abstract Java class, for example `NativeBatteryHealthSpec` | Android | Extend it in Kotlin or Java and implement each method | | Objective-C++ protocol and glue, in headers named after `codegenConfig.name` | iOS | Make your module class conform to it | The Java class is named after the spec file with `Spec` appended, and is placed in the package set by `codegenConfig.android.javaPackageName`. The generated C++ and Objective-C++ files are named after `codegenConfig.name`. ## What the spec guarantees, and what it does not 1. **One source of truth.** Both platforms' interfaces come from the same file, so they cannot drift apart. 2. **Native code must match.** On Android your module will not compile until it implements the generated abstract methods; on iOS the compiler flags methods the generated protocol requires but your class lacks. 3. **Typed JavaScript call sites.** TypeScript checks calls against `Spec`, but only when the type checker runs; types are erased from the running JavaScript. 4. **Not behaviour.** The spec fixes names and types, not what the native code does, which threads it uses, or how it handles errors. ## A spec-first workflow For the battery-health module, a team typically works in this order: 1. **Agree the interface** between the JavaScript and native engineers: method names, parameters, which calls return values directly and which return Promises. 2. **Write `specs/NativeBatteryHealth.ts`** and point `codegenConfig.jsSrcsDir` at the `specs` folder. 3. **Run Codegen** (it runs on every native build, or on demand through the community CLI's `codegen` command) and read the generated interfaces. 4. **Implement** the Android class that extends `NativeBatteryHealthSpec` and the iOS class that conforms to the generated protocol. 5. **Call it from JavaScript** by importing the spec's default export. Because step 1 produces a file both sides compile against, the JavaScript screen and the two native implementations can be built in parallel. ## Where it sits among the alternatives - **Legacy native modules** (no spec) still run through React Native's interop layer, but they miss the generated type safety and synchronous calls; new modules should not be written that way. - **Codegen is not mandatory.** React Native's docs note that you could write the generated code by hand; in practice nobody wants to, which is why the spec-first flow is the documented path. ## Summary The spec is a small typed file, `NativeXxx.ts` with an interface named `Spec`, that describes a native module once. Codegen reads it during the build and generates the C++ glue and the Android and iOS interfaces your native code must implement, which is how the New Architecture keeps JavaScript and two native codebases in agreement.
- Why is it called Spec and not BatteryHealthSpec?Codegen's parser requires the interface extending `TurboModule` to be named exactly `Spec`, and a spec file may declare only one such interface. The distinguishing name comes from the file (`NativeBatteryHealth.ts` produces `NativeBatteryHealthSpec` on Android) and from the string passed to `TurboModuleRegistry`.
- If a teammate calls getBatteryLevel('now') in TypeScript, where is the mistake caught?By the TypeScript type checker, because the call is checked against `Spec`, where `getBatteryLevel` takes no arguments. The check happens at type-check time in the editor or CI; the types are erased from the JavaScript that runs, so a project that never runs the type checker would not catch it there.
A spec is an architect's floor plan handed to two builders: Codegen turns it into a frame for each site, and each builder fills the rooms, so the two houses cannot end up with different doors.
saying these in an interview costs you the question
- A Turbo Native Module spec is written in Kotlin or Swift first
- Codegen runs when Metro bundles the JavaScript
- The spec enforces argument types at runtime inside JavaScript
- The spec also defines how the native code behaves
- New modules should expose methods through NativeModules without a spec