React Native Expo skill for Codex

Native apps you can regenerate from config. Codex first works out whether the project uses Continuous Native Generation or committed native folders, then changes the right layer: config plugins instead of hand-edited Info.plist, expo install instead of guessed versions, and a local Expo module instead of patched native code. SDK upgrades run one version at a time with expo-doctor and a release build on both platforms.

  • Mobile & Desktop
  • $react-native-expo
  • 233 lines
  • 2 files
  • ~2,350 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- react-native-expo

Installs to ~/.agents/skills/react-native-expo/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $react-native-expo, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and maintains Expo React Native apps - app.config.ts, config plugins, Continuous Native Generation with expo prebuild, development builds, Expo Modules API native code in Swift and Kotlin, and SDK upgrades with expo install --fix and expo-doctor. Use when creating an Expo app, adding a native library or permission, writing a config plugin or local native module, deciding whether to commit ios/ and android/, or upgrading to a new Expo SDK and React Native version.

Use it when

  • Starting or restructuring an Expo app and its app.config.ts
  • A library or permission needs native configuration
  • You need custom Swift or Kotlin code exposed to JavaScript
  • Moving the app to a newer Expo SDK and React Native release

Use something else when

How to check the result

npx expo-doctor reports no issues, npx expo export bundles both platforms, and a Release-configuration build launches on an iOS simulator and an Android emulator.

The mistake it prevents

Hand-editing ios/ or android/ in a project that regenerates them with prebuild, so the change silently disappears on the next build.

~/.agents/skills/react-native-expo/SKILL.md

--- name: react-native-expo description: Builds and maintains Expo React Native apps - app.config.ts, config plugins, Continuous Native Generation with expo prebuild, development builds,… license: MIT ---

Done well means: native projects are reproducible from app.config.ts plus config plugins, every dependency version is the one the current SDK expects, native code lives in Expo modules rather than hand-edited ios/ and android/ folders, and npx expo-doctor is clean.

Before changing anything

cat AGENTS.md 2>/dev/null                  # package manager, scripts, test command
cat package.json | grep -E '"expo"|"react-native"|"react"'
ls app.json app.config.* eas.json 2>/dev/null
git ls-files ios android | head -3         # empty output => CNG project (native dirs generated)
npx expo-doctor                            # needs network for version checks

Decide the project mode first, because every later instruction depends on it:

FindingModeRule
ios/ and android/ gitignored or absentContinuous Native Generation (CNG)Never edit native files; change config or plugins, then npx expo prebuild --clean
Native dirs committed, no prebuild in scriptsBare / manually managedEdit native files directly; config plugins no longer apply automatically
Native dirs committed and plugins in app configDrifted hybridAsk the user which one is the source of truth before editing either

Default to CNG for new work. Commit native folders only when a dependency truly needs hand-maintained native code that no plugin can express.

Install dependencies the Expo way

npx expo install expo-camera expo-image      # picks versions compatible with the SDK
npx expo install --check                     # list mismatches
npx expo install --fix                       # align them
  • Never npm install some-native-lib@latest in an Expo app; the SDK pins compatible native versions. expo install delegates to the detected package manager (npm, yarn, pnpm, bun) - respect the lockfile in the repo.
  • A library with native code does not work in Expo Go. Use a development build: npx expo install expo-dev-client, then npx expo run:ios / npx expo run:android locally or an EAS development build ($expo-eas-builds).
  • Installs need network. In a Codex sandbox with network off, ask for approval rather than editing package.json by hand with guessed versions.

App config

Prefer app.config.ts once anything is dynamic (env-specific bundle IDs, secrets read at build time):

import type { ExpoConfig, ConfigContext } from 'expo/config';

const IS_PREVIEW = process.env.APP_VARIANT === 'preview';

export default ({ config }: ConfigContext): ExpoConfig => ({
  ...config,
  name: IS_PREVIEW ? 'Acme (Preview)' : 'Acme',
  slug: 'acme',
  scheme: 'acme',
  version: '2.4.0',
  ios: {
    bundleIdentifier: IS_PREVIEW ? 'com.acme.app.preview' : 'com.acme.app',
    infoPlist: { NSCameraUsageDescription: 'Scan receipts with your camera.' },
  },
  android: { package: IS_PREVIEW ? 'com.acme.app.preview' : 'com.acme.app' },
  plugins: [
    'expo-router',
    ['expo-camera', { cameraPermission: 'Scan receipts with your camera.' }],
    './plugins/withAcmeManifest',
  ],
});
  • npx expo config --type public prints the resolved config: use it to check what a build will actually see.
  • Anything in the config is embedded in the app bundle and readable by users. Never put API secrets there; only public identifiers.
  • Permission strings must explain the user benefit; vague strings are an App Review rejection.

Config plugins

Write a plugin when a native setting is not exposed by app config or a library’s own plugin.

// plugins/withAcmeManifest.ts
import { ConfigPlugin, withAndroidManifest, withInfoPlist } from 'expo/config-plugins';

const withAcmeManifest: ConfigPlugin = (config) => {
  config = withInfoPlist(config, (c) => {
    c.modResults.LSApplicationQueriesSchemes = ['comgooglemaps'];
    return c;
  });
  return withAndroidManifest(config, (c) => {
    const app = c.modResults.manifest.application?.[0];
    if (app) {
      app.$['android:largeHeap'] = 'true';
    }
    return c;
  });
};

export default withAcmeManifest;
  • Plugins must be idempotent: running prebuild twice must not duplicate entries. Check for existing values before pushing into arrays.
  • Prefer typed mods (withInfoPlist, withAndroidManifest, withGradleProperties, withPodfileProperties) over dangerous mods that regex native source files.
  • If the plugin file is TypeScript, it must be loadable by the config loader; when in doubt compile it or write it in plain JS with JSDoc types.
  • Verify with npx expo prebuild --clean --no-install and inspect the generated Info.plist / AndroidManifest.xml, then delete the native dirs again if the project is CNG.

Native code: Expo Modules API

For custom native functionality, scaffold a local module instead of patching native folders:

npx create-expo-module@latest --local      # creates modules/<name> with ios/ and android/
// modules/device-info/ios/DeviceInfoModule.swift
import ExpoModulesCore

public class DeviceInfoModule: Module {
  public func definition() -> ModuleDefinition {
    Name("DeviceInfo")
    Function("thermalState") { () -> Int in
      ProcessInfo.processInfo.thermalState.rawValue
    }
    AsyncFunction("freeDiskBytes") { () throws -> Int64 in
      let values = try URL(fileURLWithPath: NSHomeDirectory())
        .resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
      return values.volumeAvailableCapacityForImportantUsage ?? 0
    }
  }
}
// modules/device-info/android/src/main/java/expo/modules/deviceinfo/DeviceInfoModule.kt
import android.content.Context
import android.os.PowerManager
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class DeviceInfoModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("DeviceInfo")
    Function("thermalState") {
      val pm = appContext.reactContext?.getSystemService(Context.POWER_SERVICE) as? PowerManager
      pm?.currentThermalStatus ?: 0
    }
  }
}
// modules/device-info/index.ts
import { requireNativeModule } from 'expo-modules-core';
const DeviceInfo = requireNativeModule('DeviceInfo');
export const thermalState = (): number => DeviceInfo.thermalState();

Keep the JS surface typed and small; return plain values or records, not native handles. Rebuild the dev client after any native change; Fast Refresh does not reload native code.

SDK upgrades

Upgrade one SDK at a time and read the release notes for each step.

- [ ] 1. Clean git tree; note current SDK and React Native versions
- [ ] 2. npx expo install expo@^<next-sdk> --fix
- [ ] 3. npx expo-doctor; fix every reported issue
- [ ] 4. Apply breaking changes from the SDK changelog (removed packages, renamed APIs)
- [ ] 5. CNG: npx expo prebuild --clean. Bare: apply the native template diff by hand
- [ ] 6. Build both platforms (dev build), run tests, smoke-test critical flows on devices
- [ ] 7. Bump runtimeVersion if you ship OTA updates (native layer changed)
  • SDK 55 runs only on the New Architecture (the newArchEnabled flag is gone); SDK 54 was the last to allow the legacy one. Before upgrading, check every native dependency for New Architecture support (the React Native Directory flags this).
  • Removed packages break at build time, not install time: expo-av was replaced by expo-audio and expo-video. Search imports before declaring the upgrade done.
  • Bare projects: use the Native Project Upgrade Helper diff for the target SDK rather than regenerating, or you will lose hand-written native code.

Gotchas

  • Expo Go only runs the libraries bundled in it. “Works in Expo Go, crashes in the build” usually means a version mismatch fixed by expo install --fix.
  • npx expo prebuild without --clean merges into existing folders and keeps stale settings from removed plugins.
  • Environment variables reach JS only when prefixed EXPO_PUBLIC_, and they are inlined at bundle time. They are public; never prefix secrets.
  • Metro caches aggressively. After changing Babel config or env vars, npx expo start -c.
  • patch-package patches on native libraries survive installs but not version bumps; list every patch in the upgrade checklist.
  • Monorepos: SDK 52+ auto-configures Metro for workspaces; delete old custom watchFolders hacks rather than layering on top of them.

Verification

npx expo-doctor
npx tsc --noEmit
npx expo export --platform all --output-dir /tmp/expo-export   # bundles JS for both platforms
npx expo run:ios --configuration Release                       # macOS + Xcode only
npx expo run:android --variant release

A change is done when doctor is clean, the JS bundle exports for both platforms, and a release-configuration build launches on a simulator or device. Release builds catch issues debug builds hide (missing assets, minification, Hermes bytecode).

Report template

**Mode:** CNG (native dirs gitignored), SDK 55, React Native 0.83
**Changed:** added expo-camera via expo install; config plugin plugins/withAcmeManifest.ts
**Native impact:** yes - new dev build and new runtimeVersion required before OTA
**Checks:** expo-doctor clean; tsc clean; release build launches on iOS 26 sim and Pixel 8 emulator
**Follow-ups:** camera permission copy needs product review

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "React Native Expo"
  short_description: "Native apps you can regenerate from config"
  default_prompt: "$react-native-expo Add a camera permission and the expo-camera library to this Expo app"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem