# Config plugins & CNG (Continuous Native Generation)

Deep dive for the native-config-via-JS half of the pipeline. With CNG the `ios/` and
`android/` directories are **ephemeral build artifacts**, regenerated by
`npx expo prebuild` from `app.config` + the `plugins` array + autolinking. The
golden rule: **never hand-edit generated native dirs — write a plugin instead.**

## When you need a plugin

You need a config plugin whenever a native change must survive `prebuild --clean`:
an Info.plist key, an AndroidManifest permission/intent-filter, an entitlement, a
Gradle/Podfile tweak, an extra resource file. Most popular native libraries ship
their own plugin — you just add the package name (and options) to `plugins`.

## Anatomy: mods

A config plugin is a function `(config, props) => config`. It usually delegates to a
**mod** — a hook that runs during a specific prebuild phase and receives the parsed
native file in `config.modResults`:

| Mod | Edits |
|---|---|
| `withInfoPlist` | iOS `Info.plist` |
| `withEntitlementsPlist` | iOS entitlements |
| `withAndroidManifest` | `AndroidManifest.xml` |
| `withStringsXml` / `withAndroidColors` | Android resources |
| `withAppBuildGradle` / `withProjectBuildGradle` | Gradle |
| `withPodfile` (dangerous) | iOS Podfile |
| `withDangerousMod` | raw filesystem access — last resort |

```ts
// withMicPermission.ts — typed plugin with options, editing both platforms.
import {
  ConfigPlugin,
  withInfoPlist,
  withAndroidManifest,
  AndroidConfig,
} from "expo/config-plugins";

const withMicPermission: ConfigPlugin<{ reason: string }> = (config, { reason }) => {
  config = withInfoPlist(config, (cfg) => {
    cfg.modResults.NSMicrophoneUsageDescription = reason;
    return cfg;
  });
  config = withAndroidManifest(config, (cfg) => {
    AndroidConfig.Permissions.addPermission(
      cfg.modResults,
      "android.permission.RECORD_AUDIO",
    );
    return cfg;
  });
  return config;
};

export default withMicPermission;
```

```ts
// app.config.ts — wire it with options.
export default {
  expo: {
    name: "MyApp",
    runtimeVersion: { policy: "fingerprint" },
    plugins: [["./withMicPermission.ts", { reason: "Record voice memos" }]],
  },
};
```

## Mod ordering

Plugins run **top to bottom** in the `plugins` array. Order matters when one plugin
depends on a file/key another created — put the producer before the consumer.
Within a single config, base mods run before the platform mods they feed. If a key
seems to vanish, a later plugin is overwriting it; reorder or merge instead of
assigning the whole object.

## Dangerous mods

`withDangerousMod` and `withPodfile` give raw filesystem/string access and run
*outside* the structured merge, so two dangerous mods touching the same file can
clobber each other silently. Use them only when no structured mod exists, keep the
edit idempotent (check before you append), and prefer string-anchored insertion over
full-file rewrites.

## Dynamic app config

`app.config.{js,ts,mjs,cjs,mts,cts}` lets you compute config at evaluation time —
read environment variables, switch bundle ids per profile, gate plugins by env.
Keep secrets *out*: anything returned here ships in the public bundle.

```ts
export default ({ config }) => ({
  ...config,
  ios: { bundleIdentifier: process.env.APP_VARIANT === "preview" ? "com.app.pre" : "com.app" },
});
```

## Common community plugins

Most are added by package name; many auto-register so you don't even list them.
Examples: `expo-build-properties` (set iOS deployment target / Android `compileSdk`,
enable specific build flags), `expo-camera`/`expo-location` (permission strings via
plugin props), `@react-native-firebase/app` (config files + native setup),
`react-native-ble-plx` (Bluetooth permission/usage). Always read the library's
README for its plugin props rather than editing native files by hand.

## Prebuild troubleshooting

- **Change didn't appear** → you edited a generated file; move it into a plugin and
  rerun `npx expo prebuild --clean`.
- **Plugin error during prebuild** → run `npx expo prebuild --clean --platform ios`
  (or `android`) to isolate which platform's mod throws.
- **Stale native state after an upgrade** → always `prebuild --clean` (not plain
  `prebuild`) after bumping the SDK or native deps; plain prebuild merges and can
  keep stale config.
- **Verify before building** → run `expo-doctor` after editing plugins to catch
  incompatible versions before paying for a cloud build.
