skip to content

What rules must a React Native Turbo Native Module spec file such as NativeBatteryHealth.ts follow for Codegen to accept it?

level: middleimportance: must knowfreq 50%

answer

  1. file name decides discovery
  2. Native prefix, not a .d.ts
  3. one interface, named Spec
  4. one typed registry call
  5. string literal, letters, digits, underscore

basics

~10 s

Name the file Native<Something>.ts, declare exactly one interface extending TurboModule named Spec, and load it with exactly one typed TurboModuleRegistry.getEnforcing<Spec>() or get<Spec>() call whose argument is a string literal of letters, digits and underscores.

solid answer

~40 s

Codegen is strict because it parses the file statically. The **file name** must start with `Native` (component specs instead end in `NativeComponent`); files in test folders and `.d.ts` files are skipped, and a platform suffix such as `NativeBatteryHealth.android.ts` makes a platform-only spec. Inside, there must be **exactly one** interface extending `TurboModule`, and it must be **named `Spec`**; its members may be methods or non-nullable `EventEmitter` properties, nothing else. The file must contain **exactly one** module load, typed as `TurboModuleRegistry.getEnforcing<Spec>(...)` or `get<Spec>(...)`, with a **single string-literal** argument matching `^[a-zA-Z_][a-zA-Z0-9_]*$`. Breaking a rule stops the build with a parser error prefixed `Module <name>:`; a wrong file name is worse, because Codegen silently skips the file and the build fails later on a missing generated class.

code

typescript · 12 lines
typescript
// specs/BatteryHealthSpec.ts  -- skipped: file name does not start with Native
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

const MODULE = 'NativeBatteryHealth';

export interface BatteryHealth extends TurboModule { // must be named Spec
  level: number;                                      // properties are rejected
  getBatteryLevel(): number;
}

export default TurboModuleRegistry.getEnforcing<BatteryHealth>(MODULE); // needs a string literal

go deeper

for a junior

Remember the naming convention: module spec files start with Native, and the interface inside is called Spec.

for a middle

Walk through each rule: file name, a single Spec interface of methods, one typed registry call with a literal name, and the parser error each break produces.

for a senior

Recognise the silent failure: a misnamed or misplaced spec yields no Codegen error, only a later missing-class build error, and check jsSrcsDir and the file name first.

for a principal

Put spec conventions into lint or review checklists for every native module team, since the cheapest defects to fix are the ones Codegen would otherwise skip silently.

## Why the rules exist **Codegen** does not execute your spec. It finds candidate files by name, parses them into a schema, and generates native code from that schema. Everything it needs must therefore be visible statically: the interface, the types and the module name. The rules below are what the parser enforces in React Native 0.87, and each has a recognisable failure. ## Rule 1: the file name - A module spec's file name must **start with `Native`**: `NativeBatteryHealth.ts` qualifies, `BatteryHealthSpec.ts` does not. (Fabric component specs follow a different rule: the name ends in `NativeComponent`.) - Files under test folders and TypeScript declaration files (`.d.ts`) are ignored. - A **platform suffix** is allowed: `NativeBatteryHealth.android.ts` is used only when generating for Android. - The part of the name before the first dot becomes the generated module's name, which is why the Android class is `NativeBatteryHealthSpec`. The failure mode here is the nastiest: a misnamed file raises no error. Codegen skips it, no interface is generated, and the build fails later when native code cannot find the class it extends. ## Rule 2: exactly one interface, named `Spec` - The file must declare **one** interface extending `TurboModule`. Zero gives "No TypeScript interfaces extending TurboModule were detected in this NativeModule spec"; two or more gives "Every NativeModule spec file must declare exactly one NativeModule TypeScript interface". - It must be named **`Spec`**: otherwise "All TypeScript interfaces extending TurboModule must be called 'Spec'". - Its members must be **methods** or non-nullable **`EventEmitter`** properties. A plain data property such as `level: number` is rejected. ## Rule 3: exactly one typed module load 1. The file must contain **exactly one** call to `TurboModuleRegistry.getEnforcing` or `TurboModuleRegistry.get`. 2. The call must be **typed** with the spec: `getEnforcing<Spec>('NativeBatteryHealth')`. An untyped call gets "Please type this NativeModule load". 3. It takes **exactly one argument**, and that argument must be a **string literal**, not a variable or template. 4. The name must be **safe**: letters, digits and underscores, not starting with a digit. 5. A spec interface that is never loaded is an error too: "Unused NativeModule spec". ## Rule 4: supported types only Parameter and return types must come from the set Codegen can translate (React Native's appendix lists them). An unsupported annotation, a generic Codegen does not recognise, or a generic with the wrong number of type parameters is a parser error naming the offending type. ## Reading the errors | Symptom | Likely rule broken | |---|---| | Build fails: native code cannot find `NativeBatteryHealthSpec` | File not named `Native...`, or outside `jsSrcsDir` | | `must be called 'Spec'` | Interface misnamed | | `exactly one NativeModule load` | Two registry calls in one file | | `with a string literal` | Name passed through a constant | | `Please type this NativeModule load` | Missing `<Spec>` type argument | Parser errors are prefixed with `Module <name>:`, so the module that broke is named for you. Codegen collects the errors in a file but currently throws only the **first** one, so a spec with several problems reveals them one fix at a time. ## A checklist before committing a spec - The file is named `Native<Something>.ts` and sits under `jsSrcsDir`. - It declares one `Spec` interface extending `TurboModule`, containing only methods (and, where needed, `EventEmitter` properties). - It has one typed `TurboModuleRegistry` call with a literal, safe name. - Running Codegen locally produces the expected class, with no `Module <name>:` error. - The native module registers under exactly the same name. ## A note on the module name The string passed to the registry is the name the **native module registers under**. The docs' own example uses the same string for the file and the native module (`NativeLocalStorage`), which is the least surprising convention. If the Android `getName()` or the iOS registration returns a different string, the spec still compiles, but the lookup fails at runtime. ## Summary Name the file `Native...`, declare one interface called `Spec` that extends `TurboModule`, and load it once with a typed registry call and a literal, safe module name. Most rule breaks produce a clear `Module <name>:` parser error; a misnamed file produces silence and a missing class, which is the one to recognise on sight.

  • Why can't the module name be a shared constant imported from another file?
    Codegen reads the spec statically and records the name from the call's argument. It only accepts a string literal, and the parser reports an error asking for one when it sees a variable. A constant would require evaluating code, which the generator deliberately does not do.
  • How do you give Android and iOS slightly different specs?
    Use platform-suffixed spec files, such as `NativeBatteryHealth.android.ts` and `NativeBatteryHealth.ios.ts`. Codegen accepts platform-agnostic specs for every platform and platform-suffixed specs only when generating for that platform. Keep the shared surface identical so app code stays platform-neutral.

saying these in an interview costs you the question

  • Codegen finds specs by scanning for any interface extending TurboModule
  • A misnamed spec file fails with a clear Codegen error
  • The spec interface can have any name if it extends TurboModule
  • One spec file can declare several modules for convenience
  • The module name may come from an imported constant