Skip to main content
Codegen is React Native’s code generation system that automatically creates type-safe native interfaces from Flow or TypeScript specifications. It’s a cornerstone of the new architecture, enabling compile-time type safety across the JavaScript/native boundary.

What is Codegen?

Codegen analyzes your JavaScript/TypeScript component and module specs and generates:
  • C++ interfaces - Abstract base classes for TurboModules and Fabric components
  • iOS implementations - Objective-C++ glue code
  • Android implementations - Java/Kotlin JNI bindings
  • Type definitions - Consistent types across platforms
Located in packages/react-native-codegen/, Codegen consists of:
  • Parser - Extracts type information from JS/TS files
  • Schema - Intermediate representation of types
  • Generators - Platform-specific code generators

Why Codegen?

Before Codegen

Manual native module implementation:
// JavaScript
NativeModules.MyModule.doSomething("string", 42, callback);
// iOS - manual typing, prone to errors
RCT_EXPORT_METHOD(doSomething:(NSString *)str
                         num:(NSNumber *)num
                    callback:(RCTResponseSenderBlock)callback) {
  // Hope the types match!
}
// Android - separate manual typing
@ReactMethod
public void doSomething(String str, int num, Callback callback) {
  // Hope this matches iOS!
}
Problems:
  • Type mismatches caught only at runtime
  • No guarantee iOS and Android match
  • Boilerplate code for every method
  • Manual serialization logic

With Codegen

Single source of truth:
// NativeMyModule.ts - SINGLE SPEC
export interface Spec extends TurboModule {
  doSomething(
    str: string,
    num: number,
    callback: (result: string) => void
  ): void;
}
Codegen automatically generates:
  • C++ abstract interface
  • iOS Objective-C++ implementation template
  • Android Java/Kotlin JNI bindings
  • Type validation at compile time
Codegen ensures type consistency across JavaScript, iOS, Android, and any other platforms. Type errors are caught at build time, not runtime.

Architecture

Schema

Codegen uses an intermediate schema representation defined in src/CodegenSchema.js:
type SchemaType = {
  libraryName?: string,
  modules: {
    [moduleName: string]: ComponentSchema | NativeModuleSchema
  }
};

type NativeModuleSchema = {
  type: 'NativeModule',
  aliasMap: {...},
  spec: {
    properties: MethodSchema[]
  }
};

type MethodSchema = {
  name: string,
  optional: boolean,
  typeAnnotation: FunctionTypeAnnotation
};
The schema is platform-agnostic and serves as input to all generators.

Pipeline

┌─────────────────────┐
│  JavaScript/TS      │
│  Spec Files         │
└──────────┬──────────┘

           v
┌──────────┴──────────┐
│  Parser             │
│  (Flow/TypeScript)  │
└──────────┬──────────┘

           v
┌──────────┴──────────┐
│  Schema             │
│  (JSON)             │
└──────────┬──────────┘

           ├──────> C++ Generator ──────> .h/.cpp files

           ├──────> iOS Generator ──────> .mm files  

           └──────> Android Generator ──> .java/.kt files

Parsers

Two parsers support different type systems: Flow Parser (src/parsers/flow/):
// @flow
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';

export interface Spec extends TurboModule {
  +getValue: () => string;
}
TypeScript Parser (src/parsers/typescript/):
import {TurboModule} from 'react-native';

export interface Spec extends TurboModule {
  readonly getValue: () => string;
}
Both parse to the same schema format.

Generators

Platform-specific code generators:
  • C++ Generator (src/generators/modules/) - Base TurboModule interfaces
  • iOS Generator (src/generators/components/) - Objective-C++ implementations
  • Android Generator (src/generators/modules/) - Java/Kotlin JNI code

Creating a Codegen Spec

TurboModule Spec

Create a spec file with TypeScript:
// NativeCalculator.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  // Synchronous method
  add(a: number, b: number): number;
  
  // Async method
  fetchData(url: string): Promise<{data: string}>;
  
  // Method with callback
  process(input: string, callback: (result: string) => void): void;
  
  // Constants
  getConstants(): {
    PI: number;
    E: number;
  };
}

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

Fabric Component Spec

Define a native component:
// MyComponentNativeComponent.ts
import {ViewProps} from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import {Int32, DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';

interface NativeProps extends ViewProps {
  // Primitives
  enabled?: boolean;
  progress?: number;
  label?: string;
  
  // Complex types
  colors?: ReadonlyArray<string>;
  style?: {
    borderRadius?: number;
    padding?: number;
  };
  
  // Events
  onProgress?: DirectEventHandler<{
    value: number;
  }>;
}

export default codegenNativeComponent<NativeProps>('MyComponent');

Commands

Define imperative commands:
import {ViewProps} from 'react-native';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import {Int32} from 'react-native/Libraries/Types/CodegenTypes';

interface NativeProps extends ViewProps {
  // Props
}

interface NativeCommands {
  scrollTo: (viewRef: React.ElementRef<typeof NativeComponent>, x: Int32, y: Int32) => void;
  focus: (viewRef: React.ElementRef<typeof NativeComponent>) => void;
}

export const Commands = codegenNativeCommands<NativeCommands>({
  supportedCommands: ['scrollTo', 'focus']
});

const NativeComponent = codegenNativeComponent<NativeProps>('MyComponent');
export default NativeComponent;

Supported Types

Primitives

interface Spec extends TurboModule {
  // Boolean
  isEnabled(): boolean;
  
  // Numbers - all mapped to 'number' in JS
  getInt(): number;      // Generates Int32 in native
  getDouble(): number;   // Generates double in native
  getFloat(): number;    // Generates float in native
  
  // String
  getName(): string;
  
  // Void
  doSomething(): void;
}

Objects

interface Config {
  apiKey: string;
  timeout: number;
  retries?: number; // Optional
}

interface Spec extends TurboModule {
  getConfig(): Config;
  setConfig(config: Config): void;
}
Codegen generates C++ structs for object types.

Arrays

interface Spec extends TurboModule {
  // Array of primitives
  getNumbers(): Array<number>;
  
  // Array of objects
  getUsers(): Array<{id: string; name: string}>;
  
  // Readonly arrays (recommended)
  getItems(): ReadonlyArray<string>;
}

Unions (Limited Support)

interface Spec extends TurboModule {
  // String unions (enums)
  getStatus(): 'idle' | 'loading' | 'success' | 'error';
  
  // Number unions
  getCode(): 200 | 404 | 500;
}
Complex unions (e.g., string | number) are not fully supported. Use object types with type discriminators instead.

Promises

interface Spec extends TurboModule {
  // Promise with simple type
  fetchData(url: string): Promise<string>;
  
  // Promise with object type
  getUser(id: string): Promise<{
    id: string;
    name: string;
    email: string;
  }>;
  
  // Promise rejection types are not specified
  // Use standard Error in JavaScript
}

Callbacks

interface Spec extends TurboModule {
  // Single callback
  process(data: string, callback: (result: string) => void): void;
  
  // Success/error callbacks
  fetch(
    url: string,
    onSuccess: (data: string) => void,
    onError: (error: string) => void
  ): void;
}

Codegen Type Utilities

import {
  Int32,
  Float,
  Double,
  UnsafeObject,
  WithDefault,
  DirectEventHandler,
  BubblingEventHandler
} from 'react-native/Libraries/Types/CodegenTypes';

interface NativeProps extends ViewProps {
  // Specific numeric types
  count: Int32;
  opacity: Float;
  latitude: Double;
  
  // With default value
  enabled: WithDefault<boolean, false>;
  
  // Unsafe object (any structure)
  metadata?: UnsafeObject;
  
  // Event handlers
  onPress?: BubblingEventHandler<{x: number; y: number}>;
  onLoad?: DirectEventHandler<{uri: string}>;
}

Generated Code

C++ Interface

For TurboModule spec:
interface Spec extends TurboModule {
  add(a: number, b: number): number;
}
Codegen generates:
// CalculatorSpec.h
class JSI_EXPORT CalculatorSpec : public TurboModule {
protected:
  CalculatorSpec(std::shared_ptr<CallInvoker> jsInvoker);
  
public:
  virtual double add(jsi::Runtime &rt, double a, double b) = 0;
};
Your implementation:
// Calculator.h
class Calculator : public CalculatorSpec {
public:
  Calculator(std::shared_ptr<CallInvoker> jsInvoker);
  
  double add(jsi::Runtime &rt, double a, double b) override {
    return a + b;
  }
};

iOS (Objective-C++)

Codegen generates protocol and base:
// RCTCalculatorSpec.h
@protocol NativeCalculatorSpec <NSObject>
- (NSNumber *)add:(double)a b:(double)b;
@end
Your implementation:
// RCTCalculator.h
@interface RCTCalculator : NSObject <NativeCalculatorSpec>
@end

// RCTCalculator.mm
@implementation RCTCalculator

RCT_EXPORT_MODULE(Calculator)

- (NSNumber *)add:(double)a b:(double)b {
  return @(a + b);
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeCalculatorSpecJSI>(params);
}

@end

Android (Java/Kotlin)

Codegen generates abstract class:
// CalculatorSpec.java
public abstract class CalculatorSpec extends NativeTurboModuleSpec {
  public CalculatorSpec(ReactApplicationContext context) {
    super(context);
  }
  
  @ReactMethod(isBlockingSynchronousMethod = true)
  public abstract double add(double a, double b);
}
Your implementation:
// Calculator.kt
class Calculator(reactContext: ReactApplicationContext) 
  : CalculatorSpec(reactContext) {
  
  override fun getName() = "Calculator"
  
  @ReactMethod(isBlockingSynchronousMethod = true)
  override fun add(a: Double, b: Double): Double {
    return a + b
  }
}

Running Codegen

Codegen runs automatically during build:
# iOS
cd ios && pod install

# Android
cd android && ./gradlew assembleDebug
Codegen output is in:
  • iOS: ios/build/generated/ios/
  • Android: android/app/build/generated/source/codegen/

Manual

Run Codegen explicitly:
yarn react-native codegen
Or via Node:
node node_modules/react-native/scripts/generate-codegen-artifacts.js \
  --path . \
  --outputPath build/codegen

Configuration

Specify Codegen settings in package.json:
{
  "name": "my-app",
  "codegenConfig": {
    "name": "MyAppSpec",
    "type": "all",
    "jsSrcsDir": "./src",
    "android": {
      "javaPackageName": "com.myapp.specs"
    }
  }
}
Options:
  • name - Library name for generated code
  • type - "all", "modules", or "components"
  • jsSrcsDir - Directory containing spec files
  • android.javaPackageName - Java package for generated Android code

Best Practices

1. Use TypeScript Over Flow

// Recommended: TypeScript has better tooling
import {TurboModule} from 'react-native';

export interface Spec extends TurboModule {
  getValue(): string;
}

2. Prefer Readonly Types

// Good: Immutable arrays
getItems(): ReadonlyArray<Item>;

// Less ideal: Mutable arrays
getItems(): Array<Item>;

3. Use Explicit Numeric Types for Components

import {Int32, Float} from 'react-native/Libraries/Types/CodegenTypes';

interface NativeProps extends ViewProps {
  // Explicit types prevent precision issues
  count: Int32;
  progress: Float;
}

4. Provide Defaults for Optional Props

import {WithDefault} from 'react-native/Libraries/Types/CodegenTypes';

interface NativeProps extends ViewProps {
  enabled: WithDefault<boolean, true>;
  opacity: WithDefault<Float, 1.0>;
}

5. Document Complex Types

/**
 * Configuration for API client
 */
interface APIConfig {
  /**
   * Base URL for API requests
   */
  baseURL: string;
  
  /**
   * Request timeout in milliseconds
   */
  timeout: number;
}

interface Spec extends TurboModule {
  configure(config: APIConfig): void;
}
// User types
interface User {
  id: string;
  name: string;
  email: string;
}

interface UserPreferences {
  theme: 'light' | 'dark';
  notifications: boolean;
}

interface Spec extends TurboModule {
  getUser(id: string): Promise<User>;
  getUserPreferences(userId: string): Promise<UserPreferences>;
  updateUserPreferences(userId: string, prefs: UserPreferences): Promise<void>;
}

Troubleshooting

Codegen Not Running

Check:
  1. codegenConfig in package.json is correct
  2. Spec files are in jsSrcsDir
  3. Specs extend TurboModule or use codegenNativeComponent
Force regeneration:
# iOS
cd ios && rm -rf build && pod install

# Android
cd android && ./gradlew clean && ./gradlew assembleDebug

Type Errors

Error: “Unsupported type” Solution: Ensure you’re using supported types. Complex unions and tuples are not supported. Error: “Missing required prop” Solution: Make prop optional with ? or provide default with WithDefault.

Generated Code Not Found

Check output directories:
# iOS
find ios/build/generated -name "*Spec.h"

# Android
find android/app/build/generated/source/codegen -name "*.java"
If empty, Codegen didn’t run. Check build logs.

Mismatched Types

Error: “Cannot convert between types” Solution: Ensure JavaScript types match native implementation:
// Spec says Promise<string>
fetchData(): Promise<string>;
// Implementation must return NSString via promise
- (void)fetchData:(RCTPromiseResolveBlock)resolve
           reject:(RCTPromiseRejectBlock)reject {
  resolve(@"string"); // ✓ Correct
  // resolve(@123);    // ✗ Wrong type!
}

Advanced Topics

Custom Codegen Types

Extend Codegen with custom parsers:
// my-custom-parser.js
module.exports = {
  parseFile(filename, code) {
    // Custom parsing logic
    return schema;
  }
};

Multi-Platform Support

Generate code for custom platforms:
// my-platform-generator.js
function generate(schema) {
  // Generate code for custom platform
  return {
    'MyModule.cpp': cppCode,
    'MyModule.h': headerCode
  };
}

Schema Validation

Validate schemas before generation:
const {SchemaValidator} = require('@react-native/codegen');

const errors = SchemaValidator.validate(schema);
if (errors.length > 0) {
  console.error('Schema validation failed:', errors);
}

Migration Guide

From Legacy Native Modules

Before:
import {NativeModules} from 'react-native';
const {MyModule} = NativeModules;
After:
// 1. Create spec
import {TurboModule, TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  getValue(): string;
}

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

// 2. Update usage
import NativeMyModule from './specs/NativeMyModule';
const value = NativeMyModule.getValue();

From Manual Type Definitions

Before:
// Manual types, might drift from implementation
interface MyModuleType {
  getValue(): string;
}

declare const MyModule: MyModuleType;
After:
// Codegen ensures types match implementation
export interface Spec extends TurboModule {
  getValue(): string;
}

Further Reading

TurboModules

See Codegen in action with TurboModules

Fabric Renderer

Component specs for Fabric

Architecture Overview

How Codegen fits in the architecture

JavaScript Interface

JSI types used by Codegen

Build docs developers (and LLMs) love