skip to content

In an Expo app, what does npx create-expo-module --local set up, and what does expo-module.config.json tell the build?

level: juniorimportance: nice to knowfreq 20%

answer

  1. a modules directory in the app
  2. Swift, Kotlin and TypeScript stubs
  3. platforms plus class lists
  4. full Kotlin class names
  5. native edits need a rebuild

basics

~20 s

npx create-expo-module --local scaffolds a module under the app's modules directory with Swift, Kotlin and TypeScript files and an expo-module.config.json, which lists the platforms and the native module classes autolinking registers. Native changes need a rebuild.

solid answer

~40 s

Run in the app root, `npx create-expo-module@latest --local` asks for names, platforms and feature examples, then creates `modules/<name>/` with `ios/`, `android/`, `src/` and `expo-module.config.json`; it installs nothing and creates no example app. Expo Autolinking discovers modules in that directory. The config file's `platforms` array (`apple`, `android`, `web`) says where the module exists, `apple.modules` lists the Swift class names, and `android.modules` lists fully qualified Kotlin class names; those lists are what gets registered. The app then needs native projects (`npx expo prebuild --clean` if it has none) and a rebuild whenever native code or this file changes; TypeScript changes reload through Metro. It cannot run in Expo Go, so a development build is required.

code

bash · 8 lines
bash
# In the app root: scaffold a local module with two feature examples
npx create-expo-module@latest --local --features Function AsyncFunction

# First time only, if the app has no native folders yet
npx expo prebuild --clean

# Build and run a development build that includes the module
npx expo run:ios

go deeper

for a junior

Recall the command, npx create-expo-module --local, the modules directory it creates, and that native changes need a rebuild rather than a reload.

for a middle

Explain the config file's platforms and module lists, including fully qualified Android names, and diagnose Cannot find native module.

for a senior

Set up the team workflow for local modules: development builds, when to prebuild or reinstall pods, and keeping Name, class lists and JavaScript lookups aligned.

for a principal

Decide when app-specific native code stays a local module and when it graduates to a standalone package shared across apps.

## Two kinds of module `create-expo-module` creates either a **standalone module**, its own package with an example app, meant for reuse or publishing, or a **local module**, which lives inside one app and is not published. For app-specific native code, such as a custom haptics pattern API, the Expo docs recommend the local form. ## What the --local command generates Run in the directory holding the app's `package.json`: ```bash npx create-expo-module@latest --local ``` It prompts for the module name, the native module name, the **platforms** and the **feature examples** to include. Flags can answer those up front: - `--platform android apple web` selects platforms; - `--features Function AsyncFunction Event View` adds small working examples of those DSL components; - `--barrel` generates an `index.ts` barrel for a local module, which is otherwise not created. The result lands in **`modules/<name>/`**, or in the directory named by `expo.autolinking.nativeModulesDir` in `package.json`, with: | path | contents | |---|---| | `ios/` | the Swift module class and iOS build files | | `android/` | the Kotlin module class and Android build files | | `src/` | TypeScript that loads the module with `requireNativeModule` | | `expo-module.config.json` | platforms and module classes | A local module skips dependency installation and gets no example app; the host app is the test bed. ## expo-module.config.json This file tells autolinking what to register: - **`platforms`**: `android`, `apple` (or the granular `ios`, `macos`, `tvos`), `web`, and `devtools`. - **`apple.modules`**: the **Swift class names** to put in the generated modules provider. - **`apple.appDelegateSubscribers`**: Swift classes that receive AppDelegate lifecycle events. - **`android.modules`**: the **fully qualified** Kotlin class names, package plus class, for the generated package provider. Expo's own packages follow the same shape; `expo-haptics` lists `HapticsModule` under `apple.modules` and `expo.modules.haptics.HapticsModule` under `android.modules`. A class missing from these lists is never registered, so JavaScript cannot find it. ## Getting it into the running app 1. **Native projects**: if the app has no `ios/` and `android/` folders yet, run `npx expo prebuild --clean`; if it has a prebuilt `ios/`, reinstall pods with `npx pod-install`. 2. **Build**: run the app from Xcode or Android Studio, or with `npx expo run:ios` and `npx expo run:android`. 3. **Iterate**: TypeScript changes reload through the development server; **Swift or Kotlin changes need another build**, and so do new native files or edits to `expo-module.config.json` (reinstall pods on iOS). ## Inline modules, the experimental alternative Expo SDK 56 added **inline modules**, an experimental way to write Kotlin and Swift module files directly in the project directory without a separate module folder. They are enabled through `expo.experiments.inlineModules` in app config, and the docs warn the API is subject to breaking changes. For work that must stay stable, the local module created by `create-expo-module --local` remains the documented default: - it has its own `expo-module.config.json`, so registration is explicit; - it can later move to a standalone package with little change; - it follows the same DSL, so nothing learned is lost if inline modules mature. ## The error that means the module is not in the binary `requireNativeModule('HapticPatterns')` looks the module up by the string passed to `Name(...)` and throws **`Cannot find native module 'HapticPatterns'`** when it is not registered. The usual causes: - the app is running in **Expo Go**, which only contains the Expo SDK's native code, so custom modules need a **development build**; - the binary was built **before** the module was added and has not been rebuilt; - the class is missing from `expo-module.config.json`, or the `Name` differs from the string JavaScript asks for. `requireOptionalNativeModule` returns `null` instead of throwing, for code that must tolerate the module's absence, such as on the web.

  • A teammate adds a new Swift file to a local Expo module and the app still behaves as before after a Metro reload; why?
    Metro only reloads JavaScript. Swift and Kotlin are compiled into the binary, so a native change needs a rebuild, and a new native file on iOS also needs the pods reinstalled so the build includes it. Rebuilding with `npx expo run:ios` picks up both.
  • When would you create a standalone module instead of a local one?
    When the code must be shared by several apps, kept as its own package in a monorepo, or published to npm. A standalone module carries its own package metadata and an example app for developing it in isolation; a local module is simpler for code only one app uses.

saying these in an interview costs you the question

  • A local Expo module works in Expo Go after a Metro reload.
  • android.modules takes the bare class name, like apple.modules.
  • expo-module.config.json lists every exported function of the module.
  • Editing Swift code in a local module needs only a JavaScript reload.
  • requireNativeModule looks modules up by their native class name.