Codegen for TurboModules: Generating Type-Safe Interfaces
AI generated
RN
native
React Native / New Architecture
Codegen for TurboModules
Generating type-safe native interfaces from a single spec

Codegen turns a TypeScript spec file into the single source of truth for a TurboModule, generating matching interface code for iOS and Android at build time. This article walks through what codegen actually does, how a new TurboModule spec comes together in practice, and which class of bugs disappears entirely compared to hand-written bridging declarations.

10 min read Codegen TurboModules Type Safety

1. Why manual bridging was error-prone

In the old architecture, every native module was manually rebuilt with matching method signatures on iOS and Android, while JavaScript discovered which methods even existed at runtime purely through reflection or string-based lookup. A misspelled method, a forgotten parameter, or a type mismatch between the JavaScript and native implementation often only surfaced as a runtime error, sometimes only on a rarely hit code path in production.

Since both platforms were maintained independently, the iOS and Android implementation of a module frequently drifted apart over time, for example when a new parameter was only added on one platform. Codegen addresses exactly this: a single, typed spec file becomes the one source of truth from which both platforms are generated consistently.

2. How codegen works: from TypeScript spec to generated code

Codegen reads special TypeScript files at build time, recognized by convention as NativeXyz.ts, that export an interface extending TurboModule. A parser first turns this interface into a platform-independent schema representation as JSON, describing every method, parameter type and return value in a machine-readable form.

From that schema, separate codegen backends then generate the actual platform artifacts: Objective-C++ protocols and headers for iOS, Java interfaces and matching JNI bindings for Android. The generated code lands in a build directory and is regenerated on every build, which is why it should never be edited by hand.

3. Writing a TurboModule spec: NativeMyModule.ts

A spec file follows a strict, restricted subset of TypeScript so that codegen can reliably parse it. Allowed are certain primitive types, arrays, objects with a fixed shape, and a few special types like Double or UnsafeObject for cases with no fixed structure. Generic TypeScript features like union types with more than two options or complex mapped types are deliberately not supported.

The example below shows a minimal, valid spec file with one synchronous and one asynchronous method, exactly as codegen interprets it as the contract between JavaScript and the native side.


import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getDeviceId(): string;
  getBatteryLevel(): Promise<number>;
  multiply(a: number, b: number): number;
}

export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');

4. The generated code in detail: schema, headers and interfaces

For the example module, codegen produces, among other things, a JSON schema file with the abstract method description, an Objective-C++ protocol NativeMyModuleSpec with exactly matching method signatures for iOS, and a Java interface NativeMyModuleSpec for Android that the native class must implement. Both generated interfaces mirror the TypeScript spec exactly, apart from type conversion.

Importantly, these generated files are pure contracts, not implementations. The actual native class must implement the generated interface, and the compiler reports an error as soon as a method is missing or has the wrong signature, something that simply did not exist in the old, manually maintained bridging world.

5. Implementation on iOS: Objective-C++ against the generated protocol

The iOS implementation subclasses NSObject, conforms to the generated protocol NativeMyModuleSpec, and implements every method with an exactly matching signature. For asynchronous methods like getBatteryLevel, the generated code already handles conversion into a JavaScript promise, the native implementation only needs to call resolve or reject.

The module is registered through the generated factory method getTurboModule, which the TurboModuleManager calls on first access from JavaScript. Unlike old native modules, there is no manual RCT_EXPORT_METHOD macro needed for every single method, since the full method list is already known from the spec.

6. Implementation on Android: Kotlin against the generated Java interface

On Android, the Kotlin class implements the generated interface NativeMyModuleSpec, derived from the abstract base class ReactContextBaseJavaModule. Every method must exactly match the generated signature, including the Promise parameter required for asynchronous methods.

Registration happens through a TurboReactPackage that returns an instance of the class in its getModule method and supplies metadata for turbo module discovery via getReactModuleInfoProvider. If either piece is missing, the TurboModuleManager fails to find the module at runtime, but reports a clear error instead of a silent failure.

7. Registration, podspec and autolinking

For codegen to find a module at all, the podspec on iOS or the build.gradle on Android must point to the codegen configuration, usually through an entry in package.json under codegenConfig that declares the name and path of the spec files. React Native autolinking scans this configuration automatically on every build and wires in new modules without requiring manual changes to native project files.

For local modules within a monorepo that are not published as a separate npm package, a matching codegenConfig entry in the app's own package.json is enough, as long as the spec file is discoverable via the configured jsSrcsDir.

8. Type safety: which errors codegen catches at build time

Codegen mainly catches structural errors: missing methods, wrong parameter counts, incompatible types between the spec and the native implementation, and forgotten promise parameters on asynchronous methods. This class of bugs regularly caused crashes in the old architecture that only surfaced when the affected method was actually called, often far away from the actual implementation site.

What is not caught, however, are semantic errors, for example a method that according to the spec returns a valid device ID but actually returns an empty string. Codegen only checks the structure of the contract, not its content, which is why unit tests for the actual business logic remain necessary.

9. Workflow tips: monorepos, local libraries and debugging codegen errors

In a monorepo with multiple packages, it pays off to consistently bundle spec files in a dedicated specs directory per package and point the codegenConfig entry at it, instead of scattering spec files next to arbitrary other code. That makes it much easier to quickly find the affected file after a failed codegen run.

Codegen errors usually show up as cryptic parser errors with a line number in the generated intermediate representation, not directly in the spec file. In practice it helps to progressively simplify suspicious TypeScript constructs like complex union types or optional, nested objects until the codegen run succeeds again, and then reintroduce complexity in a controlled way.

Aspect Manual bridging (old) Codegen for TurboModules Concrete benefit
Source of truth Separate iOS and Android implementation One TypeScript spec file No drift between platforms
Error detection Runtime error on type mismatch Compile-time error on wrong signature Errors surface before release
Async handling Manual promise/callback mapping Automatically generated promise wrapper Less boilerplate per method
Registration RCT_EXPORT_METHOD per method Interface implementation, no macro Less manual upkeep for new methods
Monorepo fit Hard to keep in sync codegenConfig declarable per package Consistent modules across packages

Mironsoft

React Native app development and Magento integration

A mobile app for the Magento shop that actually runs smoothly?

We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.

App Concept

Plan the architecture and feature scope of a Magento-connected app together.

Magento API Integration

Cleanly connect product catalog, cart, and checkout to the shop API.

Store Publishing

Guide the App Store and Google Play release process without pitfalls.

10. Summary

Codegen for TurboModules at a Glance

Spec file

A TypeScript file with restricted syntax is the single source of truth for a TurboModule.

Codegen backends

Generate separate, platform-specific interfaces for iOS and Android from the schema.

Build-time safety

Structural errors like wrong signatures surface at compile time, not at runtime.

Limits

Codegen only checks the structure of the contract, semantic correctness remains the job of your own tests.

11. FAQ: Codegen for TurboModules at a Glance

1What is the difference between a TurboModule spec and the actual implementation?
The spec is a TypeScript file defining the contract, from which codegen generates native interfaces. The implementation is the native code that actually fulfills those generated interfaces on iOS and Android.
2Which TypeScript features does codegen support?
Only a restricted subset: primitive types, arrays, fixed object shapes and a few special types. Complex generic constructs like multi-member union types are not supported.
3Do I need to manually edit the generated code?
No, generated code is recreated on every build and should never be edited by hand, changes belong exclusively in the spec file.
4How does codegen report missing methods?
Not directly, but indirectly: if a method is missing in the native implementation, the native compiler reports a classic error about an incompletely implemented interface or protocol.
5Does codegen also work for local modules in a monorepo?
Yes, through a matching codegenConfig entry in package.json that declares the path to the spec files, even without a separate npm publication.
6What happens on a type mismatch between spec and implementation?
The native compiler stops the build, since the implementation no longer correctly fulfills the generated interface or protocol, instead of a silent runtime failure.
7Can I define asynchronous methods in the spec?
Yes, through a promise return type in TypeScript, from which codegen automatically generates the matching resolve/reject structure for iOS and Android.
8Does codegen also check the actual correctness of my methods?
No, codegen only checks the structural match between spec and implementation, not the actual logic behind it. Your own tests remain necessary for that.
9Why do I get a cryptic parser error during a codegen run?
Usually because of an unsupported TypeScript construct in the spec file. It helps to progressively simplify suspicious spots until the run succeeds again.
10Does codegen replace autolinking?
No, the two mechanisms complement each other: autolinking automatically wires modules and their codegen configuration into native projects, codegen generates the actual typed interfaces from that.